Lambda Syntax Alternatives
Jun 11
The discussion on the lambda-dev mailing list has started to address the issue of what the Java language syntax for lambdas / function literals ought to look like. Let’s look at a slightly non-trivial example and try to tease the issues out.
The Perl people have a nice example of something which uses function references in a somewhat functional way – they call it the Schwartzian Transform (but I believe it’s originally a Lisp trick sometimes called decorate-sort-undecorate). As there are just us JVM chickens here, I rewrote it in Clojure (it’s actually one of my examples in Chapter 9 of the book).
Here’s a snippet of Clojure code which defines a function to perform the Schwartzian transform. Basically, it provides a very simple way of sorting a list based on an auxiliary function (called a “keying function”) provided by the caller.
(defn schwarz [x f] (map #(nth %1 0) (sort-by #(nth %1 1) (map #(let [w %1] (list w (f w)) ) x))))
The code is doing three separate steps – creation of a list consisting of pairs (the original values paired up with the value obtained by applying the keying function to the original values), then sorting the pairs based on the values of the keying function. Finally a new list is constructed by taking only the original value from each pair in the sorted list-of-pairs (and discarding the keying function values).
What might this look like in the various proposed Java syntax variants? Let’s take a quick look at each one (note that because Java’s type system is much more static, a lot of our type declarations are more than a little long-winded):
// Strawman, with round brackets for single-expression lambdas
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map(#(T w)(makelist(w, f.apply(w))), x)
.sort(#(Pair<T, V extends Comparable<T>> l)(l.get(1)))
.map(#(Pair<T, V extends Comparable<T>> l)(l.get(0)));
}
// Strawman, with braces for all lambdas
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map(#(T w){makelist(w, f.apply(w))}, x)
.sort(#(Pair<T, V extends Comparable<T>> l){l.get(1)})
.map(#(Pair<T, V extends Comparable<T>> l){l.get(0)});
}
// BGGA
public List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return map({T w -> makelist(w, f.apply(w))}, x)
.sort({Pair<T, V extends Comparable<T>> l -> l.get(1)})
.map({Pair<T, V extends Comparable<T>> l -> l.get(0)});
}
// SotL
public List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return map(#{T w -> makelist(w, f.apply(w))}, x)
.sort(#{Pair<T, V extends Comparable<T>> l -> l.get(1)})
.map(#{Pair<T, V extends Comparable<T>> l -> l.get(0)});
}
// Redmond
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map((T w) -> {makelist(w, f.apply(w))}, x)
.sort((Pair<T,V extends Comparable<T>> l) -> {l.get(1)})
.map((Pair<T, V extends Comparable<T>> l) -> {l.get(0)});
}
How to evaluate them? My criteria are:
- Needs to start with a visible identifying mark, so that lambdas stand out from the surrounding code. The # is a handy character for this.
- Needs to use the {} delimiting syntax. Closures are a kind of block, so they should be block-like in code.
- Needs to be all in one piece, so the syntax has a visual consistency and the lambda appears as a single unit.
- Preferably, needs to have a specialized short form for function literals which take no parameters (nullary lambdas).
Based on these criteria, Redmond is the worst possible choice for me – and my experience writing Scala for the book bears this out – I found Scala’s function literals much harder to use without problems than those in other languages. BGGA is a little better, but I don’t like the lack of a simple identifying mark that tells me “Hello! I’m a lambda”.
This brings it down to a choice to between SotL and Strawman with always-brace. The choice of these two is somewhat arbitrary. Strawman-always-brace looks, to my eyes like a true Java method declaration, but with the “magic name” # – whereas SotL is genuinely new syntax, but feels closer to the Redmond and BGGA styles – so could well be an acceptable compromise for developers who are comfortable with those forms.
Pulling it all together, my preferred choices are:
- SotL
- Strawman-always-brace
- BGGA
- Strawman-single-expression-round
- Redmond
Please use the comments to tell us what you make of this issue. Of course, this won’t be in Java 7 – but it’s not too early to start thinking about Java 8 and the future.
RSS
Twitter
Email
Facebook
Join our Early Access Program, read chapters now!
Jun 13, 2011 @ 07:35:06
My favorite syntax is:
separate syntax for expresion lambdas and statement lambdas:
1) Expression Lambdas:
1.1) if len(args) expr
sample1:
list.filter( t -> t.length() > 3 )
.map( t -> t.barCount )
. max();
sample2: empty args
doRun( -> out.println(“lambda”))
1.2) if len(args) > 1: required parentheses
(args ) -> expr
Sample:
Sam2 func5 = (x, y) => x * y;
2) Statement lambdas:
2.1) if len(args) { statement* }
list.filter( t -> t.length() > 3 )
.map( t -> t.barCount )
.max();
doRun( -> { out.println(“lambda”); })
2.2) if len(args) > 1: required parentheses
(args ) -> { statement* }
Sam2 func5 = (x, y) => { return x * y;};
your sample is as:
public List schwarz(List x, Function<T, Pair<T,V extends Comparable>> f) {
return map( w -> makelist(w, f.apply(w)) , x)
.sort( l -> l.get(1) )
.map( l -> l.get(0) );
}
Ali Ebrahimi
Jun 13, 2011 @ 10:58:43
Hi Ali,
You don’t say why you like this syntax – to my eyes it’s basically the same as Redmond only even worse.
It fails *all* of my criteria, and I also fear that you’re making an assumption which isn’t true. You’re assuming that Java will be able to infer as much as Scala is about the types of parameters to lambdas. As Java won’t have “structural” function types, but will rely on SAM conversion, I don’t think that it necessarily follows that the type inference will be strong enough for all cases.
Thanks,
Ben
Jun 13, 2011 @ 12:37:48
Hi Ben,
I think my proposed syntax (similar to C# & Scala) is compact and not verbose and readable.
current lambda repository have very good type inference support. for sample this code compiles.
SAM1<SAM1<SAM1<Void,SAM1>,File>,ExceptionHandler> eachLine3 = #{exceptionHandler ->
return #{file ->
return #{ lineHandler ->
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))){
String line = null;
while((line = reader.readLine()) != null) {
lineHandler.apply(line);
}
}
} catch (IOException e) {
exceptionHandler.handle(e);
}
};
};
};
eachLine3.apply(#{I e -> }).apply(file).apply(#{line ->
System.out.println(line);
}
);
and with my proposed syntax would be as:
SAM1<SAM1<SAM1<Void,SAM1>,File>,ExceptionHandler> eachLine3 = exceptionHandler -> {
return file -> {
return lineHandler -> {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))){
String line = null;
while((line = reader.readLine()) != null) {
lineHandler.apply(line);
}
}
} catch (IOException e) {
exceptionHandler.handle(e);
}
};
};
};
eachLine3.apply( e -> ).
apply(file).apply( line -> System.out.println(line) );
Ali ebrahimi
Jun 14, 2011 @ 09:31:29
Personally I don’t see why criterion 1 is necessary aside from personal taste? Every other (JVM) language with closures has no such requirement and they are all pretty readable, Personally I like the BGGA syntax (probably not surprising since I liked the original BGGA proposal). So not only late to the game with closures/lambda you’re now going to choose some esoteric syntax rather than trying to fit with similar existing JVM language syntax. Ok you may have found scala’s a little weird doesn’t mean everybody else has, why tar them with your brush? Otherwise I’d go for SotL
Jun 14, 2011 @ 13:02:46
Hi James,
There is a bit more to my dislike of Scala’s syntax than personal taste. To illustrate, consider this “idiomatic” Scala example:
foos.filter(_.length > 3).map(_.barCount).max(from: http://mail.openjdk.java.net/pipermail/lambda-dev/2011-June/003409.html )
This contains a syntax rule which I submit is non-obvious and potentially confusing to newcomers. That is, the fact that function literals are being passed to filter() and map() and that _ is refering to the (only) argument which the function literal takes.
This is the kind of syntax that I dread trying to teach to junior developers (or offshore). If Scala ever became a serious contender for a mainstream language, this is one of the many subtle flaws that I believe would ultimately make large Scala codebases impractical to work with.
The hashmark serves two very useful purposes – it clearly marks out an area of code which will not be executed straightaway. This is a good thing, as this isn’t something which Java does elsewhere in the language. It also visually ties in with the expected method reference literal syntax. Remember also that Java does not actually have a particularly coherent concept of a block as a separate language-level concept. We want our function literals / closures to be well-defined and coherent as language concepts. Making them visually distinct from “blocks” is an important part of that.
Also, it isn’t correct to say that every other JVM language doesn’t use an identifying mark to start a function literal. Have a look at the Clojure example in my post.
Thanks,
Ben
Jun 14, 2011 @ 14:37:17
I didn’t say _every other_ JVM language, and actually I think Clojure is one of the odd ones out. I do take your point however that there is variation among the languages. Nevertheless I still hold that the style you propose is another odd-one-out and designed to shove “THE NEW FEATURES” in your face rather than to attain some higher level of code cleanliness-maintainability. Groovy and Scala both do fine without it…
And back to Scala I don’t see your issue
The underscore is not so dissimilar to Groovy’s ‘it’ placeholder. I’ve taught plenty of new programmers functional programming languages (more Haskell than Scala atm) and they have had little difficulty coming to terms with this style of syntax. That said most of the people who have had problems have been Java programmers to begin with. Telling people a type of coding is too difficult belittles both you as a teacher and them as a student. Hence I strongly contest what you claim about larger codebases in Scala and IMO its not a flaw but a feature.
To be honest I don’t think what you choose matters, the generics mess makes the whole thing near unreadable anyway….
Jun 14, 2011 @ 16:16:01
Hi James,
If you re-read your original comment, you’ll see that you said: “Personally I don’t see why criterion 1 is necessary aside from personal taste? Every other (JVM) language with closures has no such requirement”.
Also, please don’t put words into my mouth. Nowhere did I say that I thought FP was not something that should be taught to everyone. My point is that a syntax which has a visible “this is a lambda” clue will be easier for newcomers to learn and get comfortable with.
To the issue of larger Scala codebases – the simple plain fact is that compared to Java there are very few production codebases which are solely / primarily written in Scala. There are even fewer large ones. This means that we don’t have a lot of experience yet with actual Scala projects at scale.
However, other languages which had a similar arc in terms of feature addition can potentially give us some signposts. That experience teaches us that some syntax features which seem good for small codebases can end up being major hinderances as systems get larger. The evolution of Scala so far has given me some serious misgivings about Scala’s suitability for large-scale (say 500+ kloc) systems. However, time will tell – in another 3-4 years, I would think that we would have seen enough large systems built in Scala to be able to draw some more well-grounded conclusions.
Ben
Jun 15, 2011 @ 14:22:00
I’d say that curlies themselves are visible “this is a lambda” cue. The hash mark seems redundant for me. Therefore I consider BGGA as (almost) perfect option.
Jun 17, 2011 @ 04:57:08
Yes, that’s why the subtleties about whether to have special forms for 1-liner / expression lambdas are important – because there are versions of the no-hashmark syntaxes which lose the curlies for 1-liners – and I think this makes the resulting forms hard to pick out from code.
Ben
Jun 15, 2011 @ 16:17:34
Ok maybe I did say that
What I meant by that sentence was that having a unique identifier for the beginning of a closure was not something AFAIA that was a criterion for the language design w.r.t syntax elsewhere. Clojure does, however Lisp (common or scheme) which it is based on doesn’t hence why I said I consider it an odd-one-out. In fact AFAIK few languages which support notions of closures or lambdas require special treatment of them. I’m sure this is to do with how close to first class citizens functions can be but I still stand by my opinion.
I didn’t say that you implied FP wasn’t something to be taught, just that saying things like “Oh this is complex I dread teaching it” is bad for student and teacher. Its all too popular today to say things like “oh that’s too complex”, “its too dense how can people understand it”, especially w.r.t Scala. I “remember” people being equally disparaging about Java when it started gaining popularity albeit for different reasons.
Jun 15, 2011 @ 17:00:51
I don’t think your criteria 1 or 2 are necessary, as C#’s lambda expression syntax (which seems to be going over just fine) demonstrates. Requiring an identifying mark is just noise as the arrow syntax clearly indicates “this is a lambda”. And requiring braces for short one-line expressions is just boilerplate.
I’m really not sure why C# style syntax hasn’t been seriously discussed yet. It’s dead simple, it’s proven, and people use it and like it. I hope that we’re not trying to come up with something different simple for the sake of distinguishing Java from C#.
Jun 16, 2011 @ 09:35:36
Hi Jonathan,
Not sure why you’d say that C# syntax hasn’t been seriously discussed. It’s one of the alternatives receiving a lot of attention on the lambda-dev list at the moment and was included in Brian’s poll.
Thanks,
Ben
Jun 16, 2011 @ 16:36:39
Yes, the Redmond syntax is close to C#, but at least in Brian’s examples it uses the more verbose “statement lambda” syntax for single parameter one one line statements where, at least with C#, a more abbreviated “expression lambda” syntax is allowed which omits the parentheses, braces, and parameter type which is inferred. Since most lambda expressions (I would think) are going to be one line statements, this abbreviation is important and useful.
This brings up the difference between short and long lambdas (which has been discussed a bit on the list) that there is room for slightly different syntax with the two.
Cheers,
Jonathan
Jun 22, 2011 @ 12:54:56
SotL: #{ args -> statements}
from my POV SotL is more readable than any other examples mentioned above, because it sticks out more clearly from (non-functional) surrounding code
because both “#” and “->” are new operators and thus easier to recognize
and because the curly braces make it clear that one deals with code executable later on (as in anonymous code blocks – thus also more consistent with existing syntax)
Jul 01, 2011 @ 12:44:58
SotL or BGGA
# is good for direct visibility
but the {} are already a mark if indented like any block
public List schwarz(List x, Function<T, Pair> f) {
return map( {
T w -> makelist(w, f.apply(w))
}, x)
.sort( {
Pair<T, V extends Comparable> l -> l.get(1)
} )
.map( {
Pair<T, V extends Comparable> l -> l.get(0)
} );
}
Something like that.
It’s longer but readable.
My 2 cents
Jul 15, 2011 @ 09:12:03
To me SotL reads more naturaly, it strikes the right amount of bracing and “new-ness” with the -> instantly making it identifiable for what it is, and it generally feels less busy. There is such a thing as brace/bracket hell
Jul 22, 2011 @ 17:15:25
Quick question: why do all the options involve the invocation of the “apply” method?
e.g. f.apply() ?
What would be wrong with something like: f() ?
-will
Jul 22, 2011 @ 23:50:42
I realize this post is about what syntactic for lambda expressions should take, but I am also wondering why the syntax for the application of a function object is done via “f.apply(…)” vs. “f(…)”. Any reason we cannot have the latter?
-will