Java - Lambda Syntax Alternatives

Home

News: Java - Lambda Syntax Alternatives

  1. Java - Lambda Syntax Alternatives (10 messages)

    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

    Originally posted from Java7developer.com.  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 :-).

    Cheers,

    Ben

  2. And in Scala it is...[ Go to top ]

    Here it is in Scala, with some nice features to boot:

    * Its 4 lines of code (one of which is just a bracket)
    * Its completely typesafe; compiler stops you from passing inapplicable functions around
    * Automatically uses correct order for any type that has an Ordering
    * This is a "pimp" to any sequence container type
      - meaning that if you import this function in to your code, you could call list.schwartz(...)
      - Effectively like adding the "schwartz()" method to every sequence container type
    * Its contravariant in the container parameter type
      - So you could pass in an extractor function that operates on parameter's supertype

    implicit def toSchwartzian[B >: T, T](ts: Seq[T]) = new {
        def schwartz[C : Ordering](f: B => C): Seq[B] =
            ts zip (ts map f) sortWith {(p1,p2) => implicitly[Ordering[C]].compare(p1._2, p2._2) < 0} map (_._1)
            }

    Example usage:

    //...Normal lists got pimped...
    val list = List(10, 4, 12, 99)
    val schwartzed_identity = list.schwartz(i => i) // List(4, 10, 12, 99)
    val schwartzed_negation = list.schwartz(i => -i) // List(99, 12, 10, 4)

    //...XML element lists got pimped...
    val doc =
        <root>
            <z/>
            <d/>
            <y/>
            <a/>
        </root>
    val schwartzed_nodes = doc.child.schwartz(_.label) // ArrayBuffer(<a/>, <d/>, <y/>, <z/>)

  3. This applies to all of them, but I will demonstrate with the one that I like :)

    // BGGA

    public List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f)

    {

        return map({w -> makelist(w, f.apply(w))}, x).sort({l -> l.get(1)}).map({l -> l.get(0)});

    }

     

  4. This applies to all of them, but I will demonstrate with the one that I like :)

    // BGGA

    public List<T> schwarz(List<T> x, Function<T, Pair<T,V>> f)

    {

        return map({w -> makelist(w, f.apply(w))}, x).sort({l -> l.get(1)}).map({l -> l.get(0)});

    }

    If one of my devs wrote unreadable, unmaintainable code like that they'd be FIRED.

    If this is in Java 8, then Java 7 will be known as the last great version of Java.

    Maybe there is some way we can stall the java "community" process with a filibuster or something?

  5. If one of my devs wrote unreadable, unmaintainable code like that they'd be FIRED.

    If this is in Java 8, then Java 7 will be known as the last great version of Java.

    Maybe there is some way we can stall the java "community" process with a filibuster or something?

     

    Agreed.  That's the problem isn't it.  A lot of the advancement in technology are being driven too much by theoretical purity without enough sprinkling of commercial considerations.  Even when there are commercial considerations, they are typically from parties where technology is a profit center rather than a cost center.

  6. This applies to all of them, but I will demonstrate with the one that I like :)

    // BGGA

    public List schwarz(List x, Function> f)

    {

        return map({w -> makelist(w, f.apply(w))}, x).sort({l -> l.get(1)}).map({l -> l.get(0)});

    }

    If one of my devs wrote unreadable, unmaintainable code like that they'd be FIRED.

    If this is in Java 8, then Java 7 will be known as the last great version of Java.

    Maybe there is some way we can stall the java "community" process with a filibuster or something?

    I disagree, I find it more readable without the noise of Java types. I think the "x" is not at the right place though:

    return x.map( {w -> makelist(w, f.apply(w))} )

       .sort( {l -> l.get(1)} )

       .map( {l -> l.get(0)} );

  7. now THAT makes java start to look ugly !

  8. now THAT makes java start to look ugly !

     

    I agree.  If they add this garbage to Java they will effectively kill it as a mainstream language.

    Java's strength is it's simplicity.  If we wanted complexity, we'd all be coding on C++.

    I'd MUCH prefer simple for loops, even if they are more verbose, to this kind of 'cool' but completely cryptic 'one liners'.

  9. Closure can help writing clean, readable code too. I really miss them in java...

  10. I think calling Java simple is absurd. Java is a language for professionals, so the added complexity of closures does not hurt too much. (It does not beat the complexity of, generics or thread safety.) And in many areas it will help a lot, because Javas verbosity is an absolute pain in the a*. Of course it would be better to switch to Scala, but that'll take time in many organizations.

  11. Comparable type[ Go to top ]

    Shouldn't that be "V extends Comparable<V>"?

    Unless I misunderstand the usage of your "sort".