Blog of Danilo Pianini
Comparing lambda expressions in four JVM-hosted languages
Go functional!

A look to the syntax of lambda expressions for JVM-hosted languages

Comparing lambda expressions in four JVM-hosted languages

Which language is most compact and easy to read?

Introduction

In recent times, several languages targeting the JVM have been realized, and most of them with the goal of providing a hybrid functional / object oriented experience, that in Java has arrived only with Java 8, and it is not completely satisfactory.

Among those languages, Scala and Kotlin are those most common and used (although Groovy has received a great push from Gradle / Grails).

I used a little bit every one of them, and in this post I’d like to compare the syntax for a lambda function among Java (8+), Kotlin, Scala, and Xtend. The latter is somewhat a dead language, almost superseded by Kotlin, but it is an interesting player for what concerns the syntax of lambda expressions.

The inspiration from this post comes from the fact that, whatever language I’m using, I feel like I’d like to use another one to write some specific lambda…

A lambda with a single parameter used once

Let’s start with: a single parameter lambda, where the parameter is used once. Our baseline is Java:

final Function<Object, Object> foo = o -> o.toString();
// Or, with method reference:
final Function<Object, Object> bar = Object::toString;

I find the second option way more elegant, but, as you can see, you end up spending more or less the same length. The caveat here is: if I have a single parameter, why do I need to name it? Let’s see what Kotlin does:

val foo: (Any) -> Any = { o -> o.toString() }
// But the parameter is implicitly named "it"!
val bar: (Any) -> Any = { it.toString() }
// Kotlin supports method reference
val foobar = Any::toString // The compiler infers type Any.() -> String

There are a couple of interesting things to notice:

  1. in Kotlin, for declaring a lambda it is sufficient to write the body of the function within curly brackets, I find this rather elegant;
  2. in case a single argument is taken by the lambda function, it can be referred as it, without any explicit naming

Xtend is reminiscent of Smalltalk and very similar to Kotlin

val (Object) -> Object foo = [ o | o.toString ]
// The "it" name works!
val (Object) -> Object bar = [ it.toString ]

With respect to Kotlin, square brackets are used instead of curly, and the pipe is used in place of the “arrow”. It is basically the syntax used by Smalltalk. Also, we lose method references. I like it better than Java, but prefer Kotlin here.

Let’s look at Scala now:

val foo: (Any) => String = o => o.toString
// Underscore as parameter name!
val bar: (Any) => String = _.toString

Scala, similarly to Java, does not need any parentheses to surround the lambda expression. Additionally, if a method has no arguments, the brackets can be omitted when invoking it. This sounds really great (and partly is), but I find the final result a bit harder to read than the other languages: the combination of the type declaration after the variable name and the sequence of => ... = ... => (one in the type declaration, then the assignment, then the lambda) is not immediate to distinguish at glance. On the other hand, however, it is perfectly legal to write:

val foo: (Any) => String = { o => o.toString }
val bar = { o: Any => o.toString }

But it looks less Scala-ish to me. Where Scala shines is compactness in this case: in case an argument is used once, there is no need to name it, and _ can instead be used as replacement. Combine this with the possibility of omitting the parentheses, and you get an extremely compact syntax.

My personal preference order for this kind of lambda is Scala > Kotlin > Xtend > Java

A lambda with a single parameter used twice

As before, let’s start with Java:

final Function<Object, String> foo = o -> o.toString() + o.toString();

Actually, the method reference format is still available, but you have to have the function defined with a name somewhere.

In Kotlin, in addition to explicitly naming the object, we can use it multiple times:

val bar: (Any) -> String = { it.toString() + it.toString() }

The same holds for Xtend:

val (Object) -> String bar = [ it.toString + it.toString ]

Here the greatness of Scala’s underscores fails, as a second use of the underscore would imply a second parameter:

val foo: (Any) => String = o => o.toString + o.toString
// Or, more Kotlin-ishly
val bar = { o: Any => o.toString + o.toString }

My personal preference order for this kind of lambda is Kotlin > Xtend > Scala > Java

A lambda with two parameters used once

In case there are two parameters used once each, Scala gets back to its super compact lambdas using underscores:

val foo: (Int, Int) => Int = _ + _

Oh, joy.

In case there are multiple parameters, the it trick that favoured Xtend and Kotlin previously cannot be applied anymore. Kotlin at this point requires parameters to be explicitly named:

val bar: (Int, Int) -> Int = { a, b -> a + b }
// Or, equivalently:
val bar = { a: Int, b: Int -> a + b }

Xtend has got another card to play, though: parameters can be accessed with their positional name!

val (int, int) -> int bar = [ $1 + $2 ]

I find this syntax a very good compromise between compactness and clarity.

Java is not reported here, as parameter naming is mandatory anyway.

My personal preference order here is Scala > Xtend > Kotlin > Java

A lambda with two parameters used twice

Again, when a parameter needs to be accessed multiple times, Scala does not allow the underscore syntax, and as such we get something like:

val foo: (Int, Int) => Int = (a, b) => a * a + b * b
val bar = (a: Int, b: Int) => a * a + b * b

In my opinion, here the effort to get rid of as many parentheses as possible fired back: I find the syntax hard to understand at a first glance.

Here the positional default names for parameters used by Xtend shines:

val (int, int) -> int bar = [ $1 * $1 + $2 * $2 ]

It’s shorter and way clearer than Scala.

Kotlin, having only the it trick, needs to name its parameters as Scala and Java need to, and we end up with something like:

val bar: (Int, Int) -> Int = { a, b -> a * a + b * b }
// Or, equivalently:
val bar = { a: Int, b: Int -> a * a + b * b }

With respect to Scala and Java there is no obvious advantage, though I find the syntax to be slightly clearer. A handy feature of Kotlin when it comes to writing compact and clear lambdas is the usage of the underscore for marking unused parameters. It saves up some space, but I frankly used it rather sparingly.

My personal preference order for this kind of lambda is Xtend > Kotlin > Scala > Java

Conclusion

There are a number of syntactic strategies which allow lambda expressions to be written in a clear and compact way:

  • Parameter type inference
  • Single parameter implicit naming (it)
  • Multiple parameter implicit naming (Xtend’s positional naming)
  • Placeholder syntax (Scala’s _)
  • Markers for unused parameters (Kotlin’s _)

None of the languages in this short article implements them all, with the result that in any non-toy program there is some part of code that I’d have liked to write in another language. If having a lot of syntactic sugar for lambda expressions is useful when writing, it can be a double edged sword when reading code, or when ensuring some style consistency across a project with multiple developers.

Scala lambdas are sometimes extremely clear and compact (i find summing with _+_ beautiful), but the lack of parentheses may generate some confusion, especially with “fighting arrows” or “fighting equals”, e.g.:

val foo: (Int, Int, Int) => Boolean = (a, b, c) => a <= b && c <= a

Overall, the best compromise between readability and compactness is probably Xtend, due to the flexibility of the positional parameter naming system. It would be interesting to see the result of the same feature on Kotlin, which has a very similar syntax for the lambda expressions, and the (sometimes) handy marker for unused parameters.

All Posts about programming

INFORMATION TECHNOLOGY
programming programming languages java scala kotlin xtend

Dialogue & Discussion