Java8 Lambda Expressions – Perhaps not as sexy as intended?

Edit: Updated the lambda syntax to match that which is found in Brian Goetz’s November 17th presentation at Devoxx



In my defence of Java, one of the features I looked forward to in Java8 was the introduction of Lambda expressions. However, a lot of people are dissing the new feature as being “watered down”, or a “poor man’s lambda expressions”.  To understand why, it may help to see how Lambdas are used and implemented in a language like Scala and then contrast this to Java8′s Project Lambda adoption.

How Lambdas work in Scala

Scala gives you a nice syntax for specifying type-safe lambdas.  You can specify a Lambda type like so:

(paramType1, paramType2,...,paramTypeN) => returnType

As an example let’s look at your classic “take a number and square it” lambda:

val squareIt: (Int) => Double = (x) => x * x

The Lambda type (or Function type) in the example appears immediately after the colon but before the second equal sign.  The variable squareIt is type-compatible with a function that takes an Int and returns a Double.  If this were a powerOf function, we could make it’s type:
(Int, Int) => Double.

The syntax Scala uses for specifying the “type” of a Lambda expression; the parameter types and return type; is very readable once you get to know it. However, I could have written the above example without the nice syntactic sugar like so:

 val squareIt: Function1[Int, Double] = (x) => x * x

The Function1 trait in Scala’s core library denotes a Function type that accepts exactly one parameter.  In this case, the incoming parameter type is an Int and the return value type is a Double.  For the powerOf example it would be of type Function2[Int, Int, Double].

Open up a REPL and type the following to prove this concept to yourself:

scala> val squareIt: Function1[Int, Double] = (x) => x * x
 squareIt: (Int) => Double =
scala> squareIt(2)
 res0: Double = 4.0

Java8 vs Scala Lambdas – Function Types vs. SAM

Given Scala’s support for Function types, there is a convention Scala uses such that if an instance of a Class or Trait is being used as the subject of a function call then Scala makes sure the Class or Trait has implemented an appropriate apply method.

For instance, in Scala:

class SquareInt {
   def apply(x: Int) = x * x
}
.
.
val squareInt = new SquareInt
squareInt(2)

On the last line above, Scala is really just calling the apply method on SquareInt.  SquareInt isn’t actually a proper function, but rather a Class instead.

Expanding on this convention, when Scala sees…

val squareIt: (Int) => Double = (x) => x * x

… you can imagine Scala taking the following steps before compiling to byte-code:

  1. Make the type of the squareIt variable a Function1
  2. Create a new anonymous Class definition that extends Function1
  3. Make the apply method of this new class accept an Int as a parameter and return a Double
  4. Make the body of the apply method contain everything on the RHS of the equal sign
  5. Instantiate a new instance of this class and assign it to the squareIt variable

This convention really boils down to: If you use the squareIt variable like a function [i.e. sqaureIt(2)], Scala simply invokes the apply method of the instance.  If no appropriate apply method exists, you get red-squigglies in your IDE or compile errors.
Try it for yourself:

scala> val squareIt: (Int) => Double = (x) => x * x
squareIt: (Int) => Double = <function1>

scala> squareIt(2)
res2: Double = 4.0

scala> squareIt.apply(2)
res3: Double = 4.0

How Java8 handles Lambdas: SAMs

EventHandler has a single abstract function named handle. Types that follow this pattern are known as SAMs – Single Abstract Method types. In our case, our SAM’s handle function accepts a parameter of type Event:

public interface EventHandler {
   public void handle(Event evt);
}

Normally, in Java, I’d use Anonymous Inner Classes to pass functionality to a function which conveys events through an EventHandler interface, like a Button alerting anyone who cares to listen that it was clicked:

myButton.whenClicked(new EventHandler() {
   public void handle(Event evt) {
      out.println("I was clicked!");
   }
});

This chunk of code, in Java8, becomes the following Lambda expression:

myButton.whenClicked(Event evt -> out.println("I was clicked!"))

You can even let Java infer the type of the evt parameter by leaving out the reference to Event, it’s smart enough to figure that out by itself.  So you can imagine Java taking the following steps:

  1. Look up the expected parameter type of the whenClicked method and finds it to be EventHandler
  2. Scan EventHandler to make sure it has one, and only one, “abstract method”.  In this case, EventHandler is an interface and all methods on an interface are considered abstract.  In this case it finds the void handle(Event evt) method, and it indeed is the only method.
  3. Create an anonymous class which extends/implements EventHandler and:
    1. Converts everything on the LHS of the -> within the lambda expression into parameters for the handle method
    2. Make everything on the RHS of the -> into the body of the handle method
  4. Pass the instance of the EventHandler to the whenClicked function

The above list “sounds” almost exactly like what Scala does when converting its Lambda expressions into functions except for two key differences:

  1. There is no nice syntax for expressing the type of the Lambda expression
  2. Java has no general invocation convention concerning Lambda expressions

Point 1 has to do with the fact that in Scala we could declare:

val handler: (Event) => Unit = (evt) => println("I was clicked!")

Whereas in Java we have no “function type” per say, instead we end up having to specify the EventHandler type explicitly.  Even though, in the context of a Lambda expression, the EventHandler interface really just is a specific synonym for “a function which takes an Event and has a void return type”.

Point 2 refers to the fact that there is no general convention for invoking lambda expression in Java8 like there is in Scala. Remember, in Scala we could:

scala> val squareIt: (Int) => Double = (x) => x * x
squareIt: (Int) => Double = <function1>

scala> squareIt(2)
res2: Double = 4.0

Instead the only way we could invoke the lambda expression directly in Java8 is to invoke the proper method on the type this specific lambda expression resolves to, in our case:

EventHandler handler = (Event evt) -> out.println("I was clicked!");
handler.handle(someEvent);

Thoughts…

I’m not a compiler writer, however I suspect adding a convention to an existing language syntax is probably not a trivial task.  In the case of Java, allowing classes to be called like functions would probably be tough to shoehorn in.  As well, adding a “function type declaration syntax” (re: (Int) => Double) might also be non-trivial.

The Project Lambda authors chose to allow Lambda expressions to be compatible with *any* type of SAM, they did this so Lambdas could be compatible with older APIs without rewriting them, or adding an extra compatibility layer. They didn’t want you to code your APIs any differently in Java8 than you would have in previous versions. Instead they want us to keep on passing those Anonymous Inner Classes around, and they’ll just give us a nicer syntax in which to create them.

Making Lambdas more general…

What will happen, no doubt, is we’ll see people follow the Scala convention by creating a library of generic Function interfaces each containing a single apply method.  We can do this and still be completely compatible with how Java8 makes Lambda expressions type-compatible with SAMs.

For instance given a library of “generic function types”:

public class Functions {

   public static class Function1 {
      public RT apply(P1 p1);
   }

   public static class Function2 {
      public RT apply(P1 p1, P2 p2);
   }
.
.
. 
}

One might build an API like so:


import java.lang.function.Functions.Function1; 

 public class Button {

     private List<Function1<Event, Void>> clickHandlers = new ArrayList<>();

     /**
      * Add a handler to the list of handlers that are invoked 
      * when the button is clicked
      */
     public void whenClicked(Function1<Event, Void> handler) {
         clickHandlers.add(handler);
     }

     /**
      * Internal function which is invoked by the Event system when a
      * the button was clicked
      */
     private void wasClicked() {
         Event event = /* create event */
         for (Function1<Event, Void> handler : clickHandlers) {
             handler.apply(event);
         }
     }
}

And then use the API with the Lambda syntax:

myButton.whenClicked(evt -> out.println("I was clicked!"));

Not First Class but…

So no, functions aren’t exactly “first class” in Java8, however the new Lambda syntax will absolutely help us make our code more readable. Silver lining found.

Author: craig

Share This Post On
  • Dan

    Well explained and written. My gut feeling is that this works for /java/ and fits with the idioms and practices of java. I think scala is not the same language as java and from the ground up is conceptually different. I think the open question is if the generic approach of scala is desired should people just switch to scala. I figure this is a good adoption curve and by not trying to force java to be something that it isn’t; it’s going to give java that little bit of evolution but still be comfortable for the great unwashed coding masses.

  • Bob Foster

    Hi Dan,

    The syntax you’re using is out of date. Java 8 lambdas now look much like Scala/C#, except they use the -> arrow.

    I think you’re missing several real silver linings (there are several in addition to more readable code).

    People will definitely not be be writing library code like Scala’s. Java 8 lambdas work with existing Java code, like Runnable. They don’t need rewritten libraries to implement a special method.

    Java 8 allows interfaces to supply a default method, so that existing interfaces, e.g., the Collections classes, can be extended without breaking legacy code. And extended they will be, with a variety of functional APIs like map, filter, fold, etc.

    Finally, the collections and interfaces will allow lazy and eager, serial and parallel traversals, the latter using the fork/join library additions in Java 7.

    Brian Goetz is going to be delivering an excellent talk at Devoxx on Nov. 18. Watch for the slides afterward. I think you’ll be pleased.

    Bob

    • craig

      Hmm, I had taken the latest syntax from the Project Lambda page. I was trying to find the J1 keynote slides to see if there was anything new. Was there something I missed? I mentioned readability, type inference and backward compatibility.

      The point I was trying to make with the generic functions library (aka Scala-like functions) was that people will get tired of making specialized 1-method interfaces every time they want to pass some functionality around. They’ll want to generalize by saying “I want to pass in a lambda that accepts two parameters. Now I want one I can pass three things to, etc…”

      Thanks for the feed back, I’ll checkout Brian’s slides as I see someone below said they’re posted.

      Cheers!

  • Bob Foster

    Oops, I misspoke. The talk by Brian Goetz actually happened Nov. 16. It was titled “Language / Library co-evolution in Java SE 8″. Just browsing around, I don’t see a link to the talk or slides. Hopefully soon.

    Bob

  • cdmckay
    • craig

      Thanks folks, I’ll make the necessary edits to the examples, I appreciate it.

    • craig

      Hmmm, so which syntax is correct? The syntax from your links specify using => yet in in Wayne’s comment, the slides still use -> I’ll update to the version from the slides (i.e. -> without the #{} cruft)

  • Greg

    You dont define SAM in the article before using (at least I couldn’t find it). Maybe its obvious to most but I think its worth defining it. My googling says Single Abstract Method, that right?

    • craig

      You’re absolutely right Greg! I did have that defined, but it it was within a paragraph I had edited out of the final copy. Thanks for catching that, I’ll add it back in.

  • Pingback: Java 8:Lambda表达式试水 - ImportNew

  • Pingback: Java 8:Lambda表达式试水 | 江榭网-程序员的关注,程序员的快乐!

  • Vinyjones

    You were right JDK8 define a new package “java.util.function” with like forty functional interfaces.