Ben Evans (@kittylyst) and Martijn Verburg (@karianna) have teamed up to write "The Well-Grounded Java Developer" (Covers Java 7 and Polyglot programming on the JVM). You can find their joint account on twitter (@java7developer). Ben has been a professional developer and Open Source enthusiast since the late 90s. He has delivered world-class projects for banks, media companies and charities in that time, and currently works as an architect, lead developer and in-house Java expert at one of the world’s leading financial institutions. Martijn Verburg is a Java/JEE and open source consultant who is passionate about software craftsmanship and the creative power of technical communities. He currently is the co-leader for the London JUG, runs two open source projects (PCGen and Ikasan EIP) and is a bartender at the Javaranch. Martijn & Ben has posted 6 posts at DZone. View Full User Profile

Lambda Syntax Alternatives

06.13.2011
| 4196 views |
  • submit to reddit

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:

  1. Needs to start with a visible identifying mark, so that lambdas stand out from the surrounding code. The # is a handy character for this.
  2. Needs to use the {} delimiting syntax. Closures are a kind of block, so they should be block-like in code.
  3. Needs to be all in one piece, so the syntax has a visual consistency and the lambda appears as a single unit.
  4. 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:

  1. SotL
  2. Strawman-always-brace
  3. BGGA
  4. Strawman-single-expression-round
  5. 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.

 

From http://www.java7developer.com/blog/?p=326

Published at DZone with permission of its author, Martijn & Ben Verburg & Evans.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Artur Biesiadowski replied on Tue, 2011/06/14 - 6:35am

Lambdas will most probably support inference of argument types. Map will be added to collection interface with extension method. So far, sort methods in java have not required classes to be explicitly comparable.

I have put corrected code below. I have renamed l to p (I hate l variables, too easy to confuse with 1) and changed pair to have left/right instead of get(0) and get(1).

I would say that only real difference between that and clojure version is explicit declaration of Function in the schwarz method to expect T -> Pair<T,V> mapping, as opposed to no contract in clojure version. Please note that this is one time cost - callers can just call

schwarz(mylist, #{it -> Pair.of(it,costlyTransform(it)) });

no need to repeat type over there.

 

// Strawman, with round brackets for single-expression lambdas
public static <T extends Comparable<? super T>,V> List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return x.map(#(w)(f.apply(w)))
       .sort(#(p)(p.right))
       .map(#(p)(p.left));
}

// Strawman, with braces for all lambdas
public static <T extends Comparable<? super T>,V> List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return x.map(#(w){f.apply(w)})
       .sort(#(p){p.right})
       .map(#(p){p.left});
}

// BGGA
public static <T extends Comparable<? super T>,V> List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return x.map({w -> f.apply(w)})
       .sort({p -> p.right})
       .map({p -> p.left});
}

// SotL
public static <T extends Comparable<? super T>,V> List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return x.map(#{w -> f.apply(w)})
       .sort(#{p-> p.right})
       .map(#{p -> p.left});
}

// Redmond
public static <T extends Comparable<? super T>,V> List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f) {
return x.map((w) -> {f.apply(w)})
       .sort((p) -> {p.right})
       .map((p) -> {p.left});
}

Edit:

Added static and T extends Comparable specification for the methods.It doesn't affect the actual closure code fortunately, not the caller site.

 

Nicolas Bousquet replied on Tue, 2011/06/14 - 4:34am

"Lambdas will most probably support inference of argument types."

I really hope so! The first version in the initial post is barery usable. But doesn't the one in the comment miss the extends comparable for type V? I doubt the compiler will make it's inference system up to guess that.

Artur Biesiadowski replied on Tue, 2011/06/14 - 8:06am in response to: Nicolas Bousquet

Yes, there should be extends Comparable there. I got influenced by Arrays.sort(Object[]) which does not require comparable, but Collections.sort(List<T>) does require T to extend comparable.

Thanks for that, I'll try to edit original comment

Edit:

For completness, examples in different jvm languages:

Scala (after  http://joelneely.wordpress.com/2008/03/29/sorting-by-schwartzian-transform-in-scala/)

    
  
  

def sortBy[V, K <% Ordered[K]] (f: V => K) (vs: List[V]): List[V] = {
    (vs map f zip vs) sort (_._1 < _._1) map (_._2)
}

 

Groovy (just made up at the moment, might have errors):

 
   
  
def schwarz = {  list, f -> 
    list.collect { [it,f(it)] }. sort {it[1]} .collect {it[0]}
}

 

Martijn & Ben V... replied on Tue, 2011/06/14 - 9:12am in response to: Nicolas Bousquet

Hi Nicolas,

Yes, I very much hope that we end up with enough type inference to be able to omit type declarations from a lambda's parameter list. 

Also, this was really just a direct translation of the Clojure code to possible Java syntax. The actual detail of the API design (& e.g. extension methods for collections) will of course have a huge impact on real-world examples.

Thanks,

Ben

Qinxian Xiang replied on Sat, 2011/06/18 - 9:54am

// Qinxian, code is code
public List<T> schwarz(List<T> x, Function<T, Pair<T,V extends Comparable<T>>> f) {
return map((T w), x)  {makelist(w, f.apply(w))}
 .sort((Pair<T, V extends Comparable<T>> l)){l.get(1)})
 .map((Pair<T, V extends Comparable<T>> l)){l.get(0)});
}



//Qinxian, simplified version
public <P extends Pair<T, V extends Comparable<T>> List<T> schwarz(List<T> x, Function<T, P> f){
T w;
P l;
return map(#w, x){makelist(w, f.apply(w)}
    .sort(#l){l.get(l)}
    .map(#l){l.get(0)};
}

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.