why is this valid `maybeNull() ?: return` but thi...
# getting-started
t
why is this valid
maybeNull() ?: return
but this isn't
maybeNull() ?: { return }
j
In the second example the right-hand side of the elvis is a lambda expression (
{ ... }
sometimes delimit lambdas in Kotlin, not always code blocks). And this lambda doesn't allow non-local returns
If you wanted to express some sort of standalone code block there, you could do so by using `run`:
Copy code
maybeNull() ?: run { return }
👍 1
t
odd. it has no captures or args, why would the compiler think it's a lambda?
j
A more complex example:
Copy code
fun main() {
    val x = maybeNull() ?: run {
        println("In the null branch!")
        return
    }
    println("Got some non-null x: $x")
}

private fun maybeNull(): String? = null
https://pl.kotl.in/pGwEnQHEj
why would the compiler think it's a lambda?
Why would the compiler think it's a code block? There is no
if
or
when
or similar keyword / syntax construct that warrants a code block here. Kotlin doesn't have standalone code blocks like Java AFAIK, so those are always lambdas, they don't need any captured variables or arguments.
t
codeblocks are simpler and less less overhead than lambda or captures. I'd assume it would always try to generate the simplest code, until it finds out it can't.
j
Code blocks are completely different from lambdas, so there is no real point in comparing complexity here. The behaviour is just not the same at all. Code blocks execute immediately while lambdas are function literals that represent a value of a function type, can be passed to other functions, assigned to variables, and later executed. It's not a matter of compiler optimization, it's a language design decision to interpret which syntax in which way. Some syntax constructs allow code blocks, like
if
,
when
,
init
, function bodies, etc. If you're not using such a syntax, Kotlin consistently interprets braces as lambdas AFAICT, so it's not too surprising once you forget about arbitrary code blocks in the middle of some code flow. There is almost no use for code blocks outside syntax constructs, and every time I saw standalone code blocks in Java, it was either a mistake or a code smell that a piece of code really should have been extracted into a function.
t
Code blocks are completely different from lambdas, so there is no real point in comparing complexity here
Isn't that exactly when you want to compare complexity? When things are different? code blocks also control scope. So I think saying they are useless outside of syntax is a naive.
j
Isn't that exactly when you want to compare complexity? When things are different?
If the things you compare don't serve the same purpose, you wouldn't use them interchangeably, so I'm not sure why you would want to compare their complexity. It's like saying a class is more complex than an
if
statement - I don't quite see the point. In the context of our discussion, you mentioned I'd assume it would always try to generate the simplest code, until it finds out it can't. I don't understand how you would even define "simplest code" then here. The simplest code that does what? The simplest code among which options? The fact that you put "code block" or "lambda expression" in the list of options to compare is arbitrary, and the language design team decided there was no ambiguity here - it is just "lambda expression".
code blocks also control scope. So I think saying they are useless outside of syntax is a naive.
I'm aware. It's exactly their usage as scope-only (outside of any other syntax construct) that I consider code smells. I have never seen a situation where a standalone code block used as scope could not instead be clearer by being extracted into a function or removed. But I'd be happy to discuss one if you have an example where you consider one useful
e
using
{ }
for scoping in Kotlin (or Java) isn't super meaningful, as it doesn't let you re-bind variables and it doesn't give you RAII (note that JVM is allowed to garbage collect references even before the end of the block, if the reference will not be used later)
t
If the things you compare don't serve the same purpose, you wouldn't use them interchangeably
We're talking about an EXACT case where they can be used EXACTLY interchangeably.
using 
{ }
 for scoping in Kotlin (or Java) isn't super meaningful
I don't think that's the point (at least not for me. To me the point is
{}
have different meanings in extremely similar contexts (e.g. not being fn param, etc). Yet they mean two different things. That seems like a design flaw in the language. The usefulness of meaning A vs meaning B is irrelevant.
e
{}
always means lambda except when in specific control structures, I don't think it's confusing at all
2
t
how is
:?
not a control structure?
e
?:
takes an expression on both sides,
{}
as an expression can only be a lambda
t
isn't it just short hand for
if( thing == null)
e
if you want to think of it as shorthand, then
run { val tmp = maybeNull(); if (tmp != null) { tmp } else { { return } }
is the equivalent of what you wrote
t
right. which of those are lambdas, and which arn't?
also, they could all be just plain old codeblocks, and it would run fine.
e
run@{}
is a lambda, and
{ return }
is a lambda
no they can't, because codeblocks do not have values
2
t
I assume you mean result in a value?
what value does
{ return }
yield?
e
{ return }
is a
() -> Nothing
(or possibly a
(T) -> Nothing
,
T.() -> Nothing
, or
T.(U) -> Nothing
depending on context)
j
We're talking about an EXACT case where they can be used EXACTLY interchangeably
Not at all. That's exactly why I initially wrote that they are different, because I was expecting that maybe you had misunderstood this part. The lambda would NOT be executed immediately in this case, it would just be a value of a function type. For instance:
Copy code
val x = null ?: { println("test") }
In this case the value of
x
would be a function that prints
"test"
when invoked (
x
would be of type
() -> Unit
). But running this code would NOT print anything. If that were a regular code block, it would print.
e
in any case,
{ return }
involves a non-local return, which can only be performed from an inlined lambda, which does not match the context in your example. but that comes up after the grammar has already decided that it is a lambda.
t
does not match the context in your example. but that comes up after the grammar has already decided
I think that's the exact point I'm trying to make.
e
the only possible meaning for
{}
as an expression in Kotlin is a lambda, full stop. there are no scope-only blocks.
t
so your saying
{}
is always a lambda, even in simple if/while statments?
e
no, because that is not where an expression goes
after the
if
and
while
keywords is a controlStructureBody which is a block | statement, not an expression
t
so even in the case of
val x = if(true) {1} else {2}
that's not an expression?
e
no.
{1}
is a block, which contains one statement, which contains one expression, which is an integer literal
j
if you wanted to use lambda expressions as branches in an
if
expression you would need 2 pairs of braces
t
he only possible meaning for 
{}
 as an expression in Kotlin is a lambda, full stop
so... this isn't true then...
e
I said and highlighted expression
t
you did later, when we talking about control blocks... yes. But I think if I hadn't brought those up, you would have let the statment stand.
so you think havaing
{}
be lambdas sometimes, and blocks others, even in extreamlly simluar case like
?:
which is sold as a "short hand if statment`, is 100% good language design?
e
given the limited number of symbols on our keyboards, yes.
but who sells
?:
as a "short hand if statement"? the kotlin documentation consistently describes it as an operator
t
hmmm... I think your being an apologist at this point.
from the page on null safety:
Instead of writing the complete 
if
 expression, you can also express this with the Elvis operator `?:`:
given the limited number of symbols on our keyboards, yes.
has nothing to do with keys. The language could say "any lambda that does not yield a value is demoted as a block" blamo. done.
or used as a "fn pointer"
e
that would lead to other effects throughout the language
j
Any pair of braces could be a lambda. Not yielding a value is not a thing in Kotlin. There are
Unit
and
Nothing
types. Every block could therefore be a lambda. Thus the content of the block cannot help determine what is or isn't a lambda. However, limiting blocks to control flow syntax, and interpreting lambdas in every expression is really consistent, and honestly not that hard to grasp
e
you can compare to Scala which chooses a different interpretation of braces
which does give you
{ return }
as an immediately evaluated expression, but results in many other ambiguities throughout other uses
I mean that if we had another brace pair, we could say that
【】
always signifies blocks and
「」
always signifies lambdas. but those are not accessible on a typical Western keyboard
instead we have meaning by the position in source code, which is consistent with plenty of other parts of Kotlin grammar
t
rust uses
||
to denote a lmabda, as well as define params... which I think is much better. never liked that the params are defined inside the lambda in kotlin
{key,value-> stuff}
as apposed to
|key,value| {stuff}
which is far less ambiguous, and the exact same number of keysstrokes.
e
doesn't let you build DSLs as naturally though, unless you embrace the Ruby-like approach of calling without parentheses, which leads to other complications
TANSTAAFL