This has caught me by surprise. After studying the
Kotlin language to learn about how to best leverage this interesting new language for
jOOQ, I stumbled upon this puzzler. What do you think the following program will print?
fun main(args: Array) {
(1..5).forEach {
if (it == 3)
return
print(it)
}
print("done")
}
Well… You might have guessed wrong. The above will print:
12
It will NOT print what most people might expect:
1245done
Note to those of you who are not surprised:
The above is peculiar for someone used to working with Java 8, where the following code will indeed print
1245done
:
public static void main(String[] args) {
IntStream.rangeClosed(1, 5).forEach(it -> {
if (it == 3)
return;
System.out.print(it);
});
System.out.print("done");
}
The syntactical reason is explained in this section of the Kotlin manual:
https://kotlinlang.org/docs/reference/returns.html
In lambdas / closures, the
return
statement will not (necessarily) return from the lambda / closure, but from the immediate enclosing scope of the lambda / closure. The rationale has been kindly given to me by
Dmitry Jemerov from JetBrains in two tweets:
Cunningly, the Kotlin language has removed language-based support for Java constructs like
try-with-resources
, or the
synchronized
statement. That’s very reasonable, because these language constructs don’t necessarily belong in the language (
as we’ve previously claimed in another blog post), but could be moved to libraries instead. For example:
// try-with-resources is emulated using an
// extension function "use"
OutputStreamWriter(r.getOutputStream()).use {
it.write('a')
}
(
criticism here)
Or:
// Synchronized is a function!
val x = synchronized (lock, { computation() })
See also:
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/synchronized.html
After all, even in Java, the language feature only works because the language depends on library types, like
Iterable
(
foreach
),
AutoCloseable
(
try-with-resources
), or JVM features (monitor on each reference for
synchronized
)
So, what’s the deal with return?
Along the lines of the above rationale, when language designers want to avoid language constructs for things that can be implemented with libraries, but still want you to feel like these were language constructs, then the only reasonable meaning of
return
inside of such a “construct-ish” lambda / closure is to return from the outer scope. So, when you write something like:
fun main(args : Array) {
val lock = Object()
val x = synchronized(lock, {
if (1 == 1)
return
"1"
})
print(x)
}
The real intention is for this to be the equivalent of the following Java code:
public static void main(String[] args) {
Object lock = new Object();
String x;
synchronized (lock) {
if (1 == 1)
return;
x = "1";
}
System.out.println(x);
}
In the Java case, obviously, the
return
statement exits the
main()
method, because there is no other reasonable stack frame to return from. Unlike in Kotlin, where one might argue the lambda / closure would produce its own stack frame.
But it really doesn’t. The reason for this is the
inline
modifier on the
synchronized
function:
public inline fun <R> synchronized(lock: Any, block: () -> R): R {
monitorEnter(lock)
try {
return block()
}
finally {
monitorExit(lock)
}
}
See also:
https://kotlinlang.org/docs/reference/inline-functions.html
Which means that the
block
closure passed as an argument isn’t really a pure lambda expression, but just syntactic sugar embedded in the call-site’s scope.
Weird. Cunning. Clever. But a bit unexpected.
Is this a good idea? Or will the language designers regret this, later on? Are all lambdas / closures potentially “language construct-ish”, where such a return statement is expected to leave the outer scope? Or are there clear cases where this
inline
behaviour just makes total sense?
We’ll see. In any case, it is very interesting for a language to have chosen this path.
Liked this article?
Stay tuned for a follow-up about the exciting Kotlin language. In the meantime, read
Like this:
Like Loading...
So, Kotlin distinguishes between “lambda expressions” and “anonymous functions”. Not a choice that I’d have gone with…. I like what they did, but I wouldn’t have called them “lambda expressions”. I’d have called them “blocks” like they are in Perl.
I suspect there is a grey area between pure functions and just scopes, indeed. I personally never really understood what the formal definition of a closure might be. It seems that different people have different ideas about them.
@lukaseder: A closure if a function that has access to it’s lexical parent’s environment record even after the lexical parent has finished executing.
An environment record is all of the variables in scope at a specific point.
Yes, that’s how I understand it. But I’ve never seen a “closure” be able to jump anyplace within its lexical parent’s environment.
It is also important to note that return@forEach will produce the expected behavior. You can always add the specific label you want to return to, which is something I quite like. What gets confusing is where it returns to by default. Another thing to note is, if you are using forEach, I think the expected and functional thing to do is add .filterNot { it == 3 } to get rid of the 3 before the forEach.
Yes, interesting feature, to “return to labels”. Well, it’s kind of like
continue
…I think that
forEach
is the non-functional thing to get rid of, to start with :)I did expected “1245done”. Now, three days later of coding Kotlin I find this article again and I only expected “12”. I couldn’t make my mind to expect “1245done” anymore!! I only understood it again after reading again. I think we will get used to it, and actually with the label features I think this style gives you great power.
Great feedback, thanks – that’s good to know. So, apparently, the feature is surprising for a Java dev, but very intuitive, so it does add value.
This shouldn’t be surprising if you consider the language’s influences. Kotlin is very much Scala redesigned for Java developers. As such, it inherits ideas like Scala’s non-local return behavior. Now that Java has lambda functions this seems weird, but only because they chose the opposite design. Gafter’s lambda proposal had non-local returns too, though they had to be named and were not the default. The problem, of course, is that without sufficient intelligence the implementation is to throw an exception. That is one reason why it is not idiomatic in Scala.
OK, but with a stress on Scala or on Java? :) I would have expected Kotlin to be closer to Java here. After all, with the various labelling techniques available, the default could have been matching Java, wheras Scala-style returns are still possible.
Anyway, it’s just a default for something that is probably not everyday usage. I don’t think this is really a good nor a bad decision, just a matter of taste.