Friday, October 11, 2013

JDK 8 - Part 2 (Lambdas 1)

JDK 8 brings a couple of interesting - and obviously polarizing - new language elements. Probably the most interesting one is the adoption of JSR 335, colloquially know as "Project Lambda" openjdk.java.net/projects/lambda/. Technically, Project Lambda has a couple of consequences for the language core, but also for APIs:

The core provides notations for lambdas, an integration into the Java type system and means to secure backwards compatibility, especially for the collection classes. Of course, it will provide semantics for lambdas and their types.

On the side of APIs, it will provide ways to use lambdas for (say!) event handling and also provide higher-order functionals (map, filter, reduce, ...) to complement the standard iterator infrastructure of Java.

To keep these blog entries short, I'll break it down into several independent sections - the first here deals with topics of syntax and use of lambdas. It will also hint at issues related to typing of lambdas. Some strange phenomena in the handling of scopes for "lambda local" variables should be next, followed by higher order functionals and the problem of "Virtual Extension Methods" or "Public Defender Methods".

All the examples given here have been created with the JDK 8/NetBeans setup described in the previous delivery (http://berndok.blogspot.com/2013/10/jdk-8-under-ubuntu-part-1.html).

A personal remark first - I'll probably sound overly critical about a lot of things in JDK 8. This comes from two sources:

A) I have to teach this to students and I really like to have good (!) answers to questions, such as "What's the difference between abstract classes and Interfaces?" - I used to say something like "Interfaces cannot contain method implementations." and rant away about the difference between "interface inheritance" and "behavior inheritance" but that train just left the station!

B) From a software development standpoint I prefer to understand the language I'm working in - I like to be able to predict what my code is going to do when I'm writing it and not have to wait until the compiler or JUnit convince me otherwise. For secure software a predictable and consistent language core is essential and so is a clear semantics that any JVM (or other run-time system) has to implement. Too many exceptions from my expectations make me nervous - I have to rely on my memory to predict the effects of something I consider harmless and with my memory issues it's just too risky :-)

Lambdas - why?

Project Lambda states (http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html):

"Given the increasing relevance of callbacks and other functional-style idioms, it is important that modeling code as data in Java be as lightweight as possible. In this respect, anonymous inner classes are imperfect for a number of reasons..."

Right now I'm not quite sure about the "increasing relevance of ... other functional-style idioms", but I'm buying the callback argument. This should illustrate the issue:

JButton myButton = new JButton("Button");
       
myButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent ae) {
            System.out.println(ae + ": Button clicked");
      }});



In addition to serious questions about e. g. the use of "this" in an inner class, it's just not pretty! But of course, it's consistent with the semantics of instances and methods in the rest of Java.

The lightweight solution (as in COMMON LISP or JavaScript) involves functional objects - in JDK 8 it looks like this and it works just fine:

myButton.addActionListener(ae -> {
      System.out.println(ae + ": Button clicked");
      });


I'm sold. So, let's see what's behind lambdas in a little more depth.


Lambdas - Syntax and a first look at types

public class RunnableTest {
    public static void main(String[] args) {
        Runnable r = () -> {
            System.out.println("Hello world!");
        };
        r.run();
    }
}


This is the lambda solution to the Hello World problem. One thing becomes apparent immediately: the activation of the lambda is bound to its type (in this case Runnable). I don't know of a general function application in Java comparable to COMMON LISPs apply or funcall. Let's check that quickly:

public interface Workable {
    void work();
}

public class WorkableTest {
    public static void main(String[] args) {
        Workable r = () -> {
            System.out.println("Hello world!");
        };
        r.work();
    }
}


Works! Confirmed: To activate a lambda you need to know the Interface type! The type of a lambda is the Interface it implements.

Since casting Interface types doesn't work even if they have identical signatures, it's not even worth trying to cast lambda types. The remarkable issue here is that the expression  

() -> {System.out.println("Hello world!"); 

doesn't have a type by itself! Function literals don't have a type and hence cannot be called. But casting the literal to an Interface type works:

 ((Runnable)(() -> {System.out.println("Hello, world!");})).run();


It looks like that we have already three way to force a type on a lambda literal:

1. Cast it to the Interface type.
2. Assign it to a variable of the Interface type.
3. Pass it down as a parameter of the Interface type - else the ActionListener example wouldn't work (in slang downward funarg typing).

Does upward funarg typing (typing a lambda over a return type) work? Yep!

public class Upward {
    public static Runnable something()
    {
        return () -> { System.out.println("Hello, world!"); };
    }   
    public static void main(String[] args) {
        Upward.something().run();
    }
}


So the next method of typing a lambda is:

4. Return it as a value of the Interface type.

We need to look into these Interface types a little closer - next time!


No comments:

Post a Comment