Nick has been a passionate Java developer for 10 years now. In his day job he is working as an enterprise Java web developer. In his spare time he likes to learn new programming languages and new Java related technologies, and he regularly posts articles on his blog. Nick is a DZone MVB and is not an employee of DZone and has posted 7 posts at DZone. View Full User Profile

Lambdas in Java Preview - Part 4: Proposal Update

07.24.2010
| 5349 views |
  • submit to reddit

This is the fourth part in a series of blog posts (read part 1, part 2 and part 3) giving some practical examples of lambdas, how functional programming in Java could look like and how lambdas could affect some of the well known libraries in Java land. This part describes shortly the changes of a new proposal, that has been published while writing this series, and how it changes some of the examples in this series.

The new proposal
Last week Brian Goetz published a new proposal to add lambda expressions to Java, that builds on the previous straw-man proposal. The two biggest changes are that function types have been removed and that there will be more type inferencing than before. Also the new proposal uses a different syntax for lambda expressions, but it also states, that this doesn't reflect a final decision on the syntax. But let's have a short look at each of these points.

No more function types
At first, this sounds irritating. Without function types, how could we pass functions to other methods or functions? We need to declare them as parameters somehow. The answer is that functions or better lambda expressions have to be converted to SAM types. Remember that SAM types are single abstract method types, like interfaces with only one method or abstract classes with only one abstract method, and that lambda expressions can automatically be converted to such types. This is called SAM conversion, which I've also discussed in the first part of this series.

So, function types will be primarily replaced by SAM conversion, lambda expressions can only appear in places where they can be converted to a SAM type. I don't fully understand the reasons behind this decision, but in a way it actually makes sense. As shortly mentioned in the first part of this series, from an API designer's perspective you don't have to scratch your head anymore if your methods should take a function type or a SAM type as an argument. And SAM types would probably turn out to be more flexible, anyway.

However, this will change almost all of the examples in this series. In all places, where a method took a function as an argument, the type of this argument must be changed to a SAM type, and I've almost everywhere used function types. We'll have a look at this later, and see if it makes sense now to have some generic function interfaces for functions like Function<X,Y>.

More type inferencing
As we've also seen in the examples in this series, lambda expressions can be quite verbose, because of type declarations. The new proposal says, that basically all types in lambda expressions can be inferred by the compiler - their return types, the parameter types and the exception types. This will make lambdas much more readable.

Alternative syntax
As said, the syntax of the new proposal does not reflect a final decision, but it is much more like in Scala or Groovy in this proposal.

FileFilter ff = {file -> 
    file.getName().endsWith(".java") };
Although I personally like this arrow syntax much better, I'll stick with the #-syntax in this series because the prototype uses it.

More changes
Just for completeness, the proposal contains some more changes. this in lambdas has different semantics than before, break and continue aren't allowed in lambdas, and instead of return, yield will be used to return values from a lambda expression (there are no details on how yield exactly works). Also, there will be method references, that allow for referencing methods of an existing class or object instance.

Impact on the examples
Now, let's have a look at how the new proposal changes some examples of the previous posts. The file filtering example from part 1 looked like this:
public static File[] fileFilter(File dir, #boolean(File) matcher) {
    List<File> files = new ArrayList<File>();
    for (File f : dir.listFiles()) {
        if (matcher.(f)) files.add(f);
    }
    return files.toArray(new File[files.size()]);
}
The client code looked like this:
File[] files = fileFilter(dir, #(File f)(f.getName().endsWith(".java")));
As there will be no function types anymore, we will have to change the fileFilter, so that it takes a SAM type instead of a function type. In this case it's simple, we could simply change the second argument's type to FileFilter and call its accept() method:
public static File[] fileFilter(File dir, FileFilter matcher) {
    List<File> files = new ArrayList<File>();
    for (File f : dir.listFiles()) {
        if (matcher.accept(f)) files.add(f);
    }
    return files.toArray(new File[files.size()]);
}
The client side is still the same, though.

Now, as you might have noticed (or remembered from part 2) this is quite a bad example, because there actually is already the File.listFiles(FileFilter) method we can also call with a lambda:
File[] files = dir.listFiles(#(File f)(f.getName().endsWith(".java")));
The lambda will be converted to a FileFilter automatically in this case. But despite the fileFilter method is a bad example, it shows quite well that the removal of function types has very little impact, if there is already a corresponding SAM type.

Before we go further, here's the client side with the arrow syntax and type inference:
File[] files = fileFilter(dir, f -> { 
    f.getName().endsWith(".java") });
Now, let's see how the removal of function types changes the examples in the abscence of an appropriate SAM type. For that here is the List<T>.map function from part 3 again:
public <S> List<S> map(#S(T) f) {
    List<S> result = new ArrayList<S>();
    for (T e : this) {
        result.add(f.(e));        
    }    
    return result;
}
We need to replace its function type argument f by a SAM type. We could start by writing an interface Mapper with a single method map. This would be an interface especially for usage with the map function. But having in mind, that there are probably many more functions similar to our map function, we could create a more generic Function interface, or more specifically an interface Function1<X1,Y> which represents a function that takes exactly one argument of type X1 and returns a value of type Y.
public interface Function1<X1,Y> {
    Y apply(X1 x1);
}
With this we could change our map function taking a Function1 instead of a function type argument:
public <S> List<S> map(Function1<T,S> f) {
    List<S> result = new ArrayList<S>();
    for (T e : this) {
        result.add(f.apply(e));        
    }    
    return result;
}
Again, the client side would still be the same. The lambda expression will be converted to a Function1.
List<Integer> list = new List<Integer>(2,4,2,5);
List<String> result = list.map1(n -> {"Hello World".substring(n)});
The other examples are basically the same, so I think this is enough to show the changes that the new proposal brings by removing function types.

Finally let's have a short look at the new syntax and the stronger type inferencing. With the initial proposal a File.eachLine method, could be called like this (see also part 2).
file.eachLine(#(String line) {
  System.out.println(line);
});
This would look more like this with the new proposal's syntax:
file.eachLine(line -> {
  System.out.println(line);
});
And with some more syntactic sugar, e.g. if it was allowed to remove parentheses, leave out a lambdas argument if it just takes a single argument and call it it in this case, then it would look even more like a groovy control structure:
file.eachLine {
  System.out.println(it);
};
But this is not part of the any proposal, yet.


To summarize, the removal of function types does not have such a huge impact on the examples, and in a way it removes complexity and feels more Java-like. To me it would be quite reasonable to have generic function SAM types in the standard libraries, e.g. Function0 for a function that takes no arguments, Function1 for a function that takes one argument and so on (up to Function23, then Java would have more than Scala, yay!). Further complexity is removed by the stronger type inferencing capabilities.

 

From http://stronglytypedblog.blogspot.com/2010/07/lambdas-in-java-preview-part-4-proposal.html

Published at DZone with permission of Nick Wiedenbrueck, author and DZone MVB.

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

Tags:

Comments

J Szy replied on Sat, 2010/07/24 - 3:38pm

I have commented here almost two years ago that function types (as then proposed) would bring more harm than good to the Java type system, so it's very nice to see that the Oracle folks have got rid of them. One can only wonder if they'd have enough time to get rid of the closures idea altogether before they release J7. :-)

Gregory Smith replied on Sat, 2010/07/24 - 11:05pm

Thank you, Nick, for the fantastic series of articles on Lambdas in Java. I am very excited and optimistic about all of the changes coming in jdk 1.7. I really like your idea of having generic function SAM types in the standard libraries.

I am hopeful that Lambdas will placate most (ok, a few) of Java's critics while not over-complicating the language or making it too cryptic. But I confess to having a few misgivings.

Most of the Java programmers I know don't even have a firm grip on Generics yet. Are we sure that it's a good idea to introduce another strange new syntax into the language? I think one of Java's greatest strength's is its clarity when compared to most other programming languages. I hope that whatever form Lambda syntax finally takes will preserve that clarity.

I know that reducing boilerplate code is one of the driving motivations for these changes. If you are using vi as your primary editor, I can see how the verbosity of many Java constructs would be a problem. But for most of us, code completion and other features in modern IDEs have eliminated this as a burden.

I'm also not so sure if type inferencing is such a good idea. It's great that the compiler can infer the type, but can a human programmer who is attempting to decipher the contractor-written gobbledygook that has just been dumped in his lap infer them so easily? (This may be an opportunity for another IDE feature to help programmers – automatic type inferencing!)

I think most of what you have done here is already doable with the current syntax, albeit somewhat more verbosely (see example below). Have I missed something?

/****************************************/

public interface Function1<X,Y> {

    public Y apply(X x);

}

/****************************************/

public class MapToString implements Function1<Integer,String> {

    @Override

   public String apply(Integer x) {

       return "Hello World".substring(x);

   }

}

/****************************************/

public class MapToDate implements Function1<Integer,Date> {

    @Override

   public Date apply(Integer x) {

        GregorianCalendar gc = new GregorianCalendar();

        gc.add(Calendar.DAY_OF_WEEK, x);

        return gc.getTime();

    }

}

/****************************************/

public interface MappableList<T> extends List<T> {

    public <S> List<S> map(Function1<T,S> f1);

}

/****************************************/

public class SimpleMappableList<T> extends ArrayList<T> implements MappableList<T> {

     ...

   public <S> List<S> map(Function1<T,S> f1) {

        List<S> result = new ArrayList<S>();

        for (T e : this) {

            result.add(f1.apply(e));

        }

        return result;

    }

}

/****************************************/

 

Integer[] intarray = {2,4,2,5};

MappableList<Integer> list = new SimpleMappableList<Integer>(intarray);

List<String> resultS = list.map( new MapToString() );

System.out.println(resultS.toString());

List<Date> resultD = list.map( new MapToDate() );

System.out.println(resultD.toString());


Bob Smith replied on Sun, 2010/07/25 - 7:39pm

I can't understand why there's any doubt that *some form* of closures should be added to Java.   All modern programming languages have closures.  Even C (blocks) and C++ (C++0x) have them now for God's sake!

Annonymous inner classes just don't cut it anymore.   

If you're not convinced, look at the examples of the Fork-Join framework with and without closures.   Look at the collections examples with and without closures.   Look at what Apple's been able to accomplish with C blocks, namely, Grand Central Dispatch.   The Fork-Join framework can be even better than Grand Central Dispatch, but it's simply very onerous to use without closures.

Even the amended closures proposal seems to be a little too close to Josh Block's CICE for my liking.   The whole point of closures (as opposed to annonymous inner classes) is to be able to pass functions to methods and execute them within methods.    This latest proposal still requires some boilerplate, namely, overriding Function (or whatever the main closure interface turns out to be).

Yeah, Generics was probably implemented with too much complexity, but anybody who argues that generics have made Java too complex simply doesn't understand their power, and the design patterns that can be implemented with  them.

Neil Gafter can explain the arguments in favour of closures better than I can:

http://www.youtube.com/watch?v=0zVizaCOhME

Oh, and the argument to use a newer JVM language like Scala really isn't going to hold water in the enterprise, especially not since tooling for Scala just isn't there yet.

J Szy replied on Tue, 2010/07/27 - 4:34am in response to: Bob Smith

I can't understand why there's any doubt that *some form* of closures should be added to Java.   All modern programming languages have closures.

I have no doubt, I'm strongly convinced that any form of closures should be left for more "dynamic" languages (read SLDJ). At least any that's to leave now, since Java already has too much of it.

Yes, I know that most less successful languages have closures and I really like it this way.

Annonymous inner classes just don't cut it anymore.   

They never did. Unfortunately it's too late to drop them. 

If you're not convinced, look at the examples

This argument falls on two counts:

1. The examples are designed with specific purpose of demonstrating advantages of the technique shown;

2. The examples are too short to provide any insight of possible negative impact the proposed change could have on larger, long maintained code bases.

it's simply very onerous to use without closures.

Maybe, but the code using it will be very onerous to maintain with closures.

Ease of code maintenance is far more important than of writing it. Closures would encourage writing more "write once, never dare touch again" code which is exactly what Java should avoid.

The whole point of closures (as opposed to annonymous inner classes) is to be able to pass functions to methods and execute them within methods. 

It is indeed, but that doesn't necessarily mean that it is good to have that possibility.

anybody who argues that generics have made Java too complex simply doesn't understand their power

This, or they have spent too much time looking into Generics FAQ. Oh, and I wouldn't overestimate their power either. More than once have I concluded (with help from the FAQ) that they aren't powerful enough to achieve what I wanted to. Yes, I think that Generics are too complex for what they provide.

 

Alex(JAlexoid) ... replied on Wed, 2010/07/28 - 11:23am in response to: J Szy

The examples are too short to provide any insight of possible negative impact the proposed change could have on larger, long maintained code bases.

Since you can always compile you code using older Java language specification, this point is moot. And your whole position on Java smells of conservatism, with no constructive insight. Please bring something to the table that resembles progress so that the side that is more active in this can actually see what you are proposing.

Java language has not been essentially changed in over 6 years now. API's have changed, but not language. That starts to smell like COBOL.

J Szy replied on Thu, 2010/07/29 - 3:56am

[please ignore]

J Szy replied on Thu, 2010/07/29 - 3:56am in response to: Alex(JAlexoid) Panzin

Since you can always compile you code using older Java language specification, this point is moot.

RLY? Can maintenance guys maintain your code using older Java specs as well?

your whole position on Java smells of conservatism, with no constructive insight.

Not really. If you read my older comments you'd see that there are a few things I'd like to see in Java that aren't there. But since the post was about closures I write about closures.

Please bring something to the table that resembles progress so that the side that is more active in this can actually see what you are proposing.

About closures? I propose no change.

Java language has not been essentially changed in over 6 years now.

That's not necessarily bad. While there are a few things I think Java could have, I believe that changing just for the sake of change is nonsense.

And I don't see many businesses switching to Ruby or any other SLDJ just to have frequent changes in language.

API's have changed, but not language.

Sometimes even APIs have changed too much (like JDBC in J6). This probably means that any change should be really thought of with emphasis on possible negative consequences.

That starts to smell like COBOL.

Could you be less emotional please? Calling me a conservatist, or writing about COBOL doesn't really bring anything positive into the discussion and I wouldn't like it degraded to a flame war.

Rehman Khan replied on Sat, 2012/02/25 - 4:01am

Great summary! Do you know if they will be adding things like map/filter to Collections API in Java 7? If so, will it be in the "immutable" style of your example, or will map() alter the collection?

Comment viewing options

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