https://kotlinlang.org logo
#getting-started
Title
# getting-started
t

TwoClocks

02/21/2022, 11:07 PM
why is this valid
maybeNull() ?: return
but this isn't
maybeNull() ?: { return }
j

Joffrey

02/21/2022, 11:10 PM
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

TwoClocks

02/21/2022, 11:13 PM
odd. it has no captures or args, why would the compiler think it's a lambda?
j

Joffrey

02/21/2022, 11:13 PM
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

TwoClocks

02/21/2022, 11:19 PM
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

Joffrey

02/21/2022, 11:29 PM
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

TwoClocks

02/21/2022, 11:35 PM
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

Joffrey

02/21/2022, 11:54 PM
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

ephemient

02/22/2022, 1:19 AM
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

TwoClocks

02/22/2022, 2:44 AM
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

ephemient

02/22/2022, 2:52 AM
{}
always means lambda except when in specific control structures, I don't think it's confusing at all
2
t

TwoClocks

02/22/2022, 2:52 AM
how is
:?
not a control structure?
e

ephemient

02/22/2022, 2:52 AM
?:
takes an expression on both sides,
{}
as an expression can only be a lambda
t

TwoClocks

02/22/2022, 2:53 AM
isn't it just short hand for
if( thing == null)
e

ephemient

02/22/2022, 2:54 AM
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

TwoClocks

02/22/2022, 2:56 AM
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

ephemient

02/22/2022, 2:56 AM
run@{}
is a lambda, and
{ return }
is a lambda
no they can't, because codeblocks do not have values
2
t

TwoClocks

02/22/2022, 2:58 AM
I assume you mean result in a value?
what value does
{ return }
yield?
e

ephemient

02/22/2022, 3:01 AM
{ return }
is a
() -> Nothing
(or possibly a
(T) -> Nothing
,
T.() -> Nothing
, or
T.(U) -> Nothing
depending on context)
j

Joffrey

02/22/2022, 3:01 AM
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

ephemient

02/22/2022, 3:06 AM
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

TwoClocks

02/22/2022, 3:09 AM
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

ephemient

02/22/2022, 3:10 AM
the only possible meaning for
{}
as an expression in Kotlin is a lambda, full stop. there are no scope-only blocks.
t

TwoClocks

02/22/2022, 3:12 AM
so your saying
{}
is always a lambda, even in simple if/while statments?
e

ephemient

02/22/2022, 3:12 AM
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

TwoClocks

02/22/2022, 3:14 AM
so even in the case of
val x = if(true) {1} else {2}
that's not an expression?
e

ephemient

02/22/2022, 3:15 AM
no.
{1}
is a block, which contains one statement, which contains one expression, which is an integer literal
j

Joffrey

02/22/2022, 3:15 AM
if you wanted to use lambda expressions as branches in an
if
expression you would need 2 pairs of braces
t

TwoClocks

02/22/2022, 3:17 AM
he only possible meaning for 
{}
 as an expression in Kotlin is a lambda, full stop
so... this isn't true then...
e

ephemient

02/22/2022, 3:17 AM
I said and highlighted expression
t

TwoClocks

02/22/2022, 3:18 AM
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

ephemient

02/22/2022, 3:20 AM
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

TwoClocks

02/22/2022, 3:24 AM
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

ephemient

02/22/2022, 3:31 AM
that would lead to other effects throughout the language
j

Joffrey

02/22/2022, 3:31 AM
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

ephemient

02/22/2022, 3:32 AM
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

TwoClocks

02/22/2022, 3:48 AM
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

ephemient

02/22/2022, 4:07 AM
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
2 Views