Sunday, October 28, 2007

Java Closures: first prototype

I've finally had some time to make progress on a prototype of closures. If you want to see what an API looks like, you can compare Doug Lea's jsr166y fork-join framework to the same API ported to use the language features of the prototype.

If you want to try it, you can download an executable version of the prototype here. Make sure a JDK6 version of java and javac are on your path. This is binary-licensed under the JRL, but if a JSR is created I expect to license it under GPLv2. There are a few small test cases included.

This prototype supports

  • the BGGA function type syntax
  • closure literals
  • the closure conversion
  • the null type, disjunctive types, and exception transparency
  • definite assignment
  • Unreachable and completion transparency.
  • Catching multiple exceptions at once like catch(X1|X2 ex) { ...
    [not closely related to closures but the implementation was simple once closures are there]

This prototype does not yet support

  • a closure using a mutated variable from the enclosing scope
  • nonlocal control-flow (break, return, and continue)
  • the control invocation statement and loop abstractions

I'm intentionally distributing it before these features are available. The idea is that people can try this version, and compare it to the next version with these features working.

Separately, I'm working on a set of smaller language extensions for JDK7, some of which interact very nicely with Closures. For example, "extension methods" enable you to have the effect of adding methods to existing interfaces (e.g. adding "each", "filter", etc to Collection) without breaking backward compatibility. I'll write more about these over the next few days.

This is still rough around the edges, but any feedback you have is most welcome.

83 comments:

Unknown said...

Wow, this is great!
Just one question: Why there is no KSL branch for this to follow evolution/mailing list and so on?

eirikma said...

This is indeed a milestone!
Finally, the last usefull feature from Simula-67 (the very first OO language) is incorporated into Java.

A sad thing the syntax has been taken from a different family of programming langugaes, making it less intelligible for novices without a functional / academic background like myself. For instance in the JSR 166-example class ParallelDoubleArray.WithBounds you can find the function

void cumulate({double,double=>double} reducer, double base)

If this is read with the usual assumtion that commas have the lowest binding priority, I would suggest the following immediate interpretation: There is a method called cumulate that takes as parameters, eh .... The last paramter is at least an ordinary double value. The first is, something array-like, no, wait, it is one of those new function things. It's like a method that, eh... at least has a last parameter that is turned into a double, it probably takes a double as parameter and returns a double. And before that there is something other going on with an other double value. It might be related to the last, but it doesn't appear to be part of the same either. Perhaps the function optionally can receive a double and not return anything? Sounds weird.

Now, there IS already a syntax for describing functions that take parameters and return values in Java: it is the syntax used to declare abstract methods and interface members. I think the choice not to re-use this syntax is a mistake that will make many estranged from closures. Too bad, since it is such a wonderfull tool. The method declaration should have been like this:

void cumulate(double reduce(double currentChoice, double nextCandidateValue), double base)
with giving names (for clarity) to the parameters sent into the reducer function from the implementation of cumulate being optional.

Anonymous said...

Do you think there's a chance for closures to make it to Java 7?

Brian Oxley said...

It would be nice for you to also support the method reference syntax:

http://docs.google.com/View?docid=ddhp95vd_6hg3qhc

A.k.a. javadoc link -- happyCoder#postBlogComment()

Anonymous said...

I'm startled by the NullPointerException raised by an Unreachable null value. I understand you want control flow to never reach an Unreachable value.

But the consequence is that a change in typing now changes the runtime semantics of the program - as a fan of optional typing I find that rather unfortunate.

Unreachable1.<Unreachable>f(); // throws
Unreachable1.<Object>f(); // doesn't

My take: allow Unreachable nulls. I don't see where you actually want to write that type down anyway.

Neal Gafter said...

Matthias: the (conservative) specification for reachable statements would be undermined by a statement that is checked as if it cannot complete normally but which could actually complete normally.

Unknown said...

Are definitions like the following not supported?

{ int[] => int }

{ int... => int }

or am I just not going about it the right way?

Neal Gafter said...

Quintesse: varargs are not allowed, but int[] should work. I will fix this bug this evening.

Anonymous said...

to be honest, this is the most ugliest code i've seen so far. too bad that java is turning into some less understandable creature:( the readability will be near to 0 with all the generics, closures and anonymous classes... nothing against new features. but the java puzzlers is great stuff neal, anyway. thanks for it:)

Unknown said...

@goddard: maybe he's doing all this on purpose so he'll have a source for new java puzzlers for years to come! ;)

But with generics and now closures I find myself wishing more and more for typedefs so I can turn those page-long definitions into something more readable.

Mark Mahieu said...

I agree, this is great stuff - it makes such a difference to have an implementation to explore ideas with.

Incidentally, should I be able to get away with the following (which compiles, but fails at run-time with a ClassCastException)?


public class Contrived {
    
    interface Parser<T extends Number> {
        
        T parse(String s, { String => T } fn);
    }
    
    static class IntParser implements Parser<Integer> {
        
        public Integer parse(String s, {String => Integer} fn) {
            return fn.invoke(s);
        }
    }
    
    public static void main(String[] args) {
        
        Parser<? extends Number> p = new IntParser();
        p.parse("1", { String s => Double.valueOf(s) });
    }
}

Neal Gafter said...

Mark: it is a bug that the compiler accepts that code.

Anonymous said...

@quinttese: i hope so:)
typedefs are ok, but i have problems reading the new closures. it's just a cosmetics:) but for beginners, it starts to be difficlt.

Philipp Meier said...

+1 for eirikma from me. The syntax {double,double => double} is indead plain ugly and does not match the declaration of methods in java. I prefer "double reduce(double first, double second)" over the syntax without names.

From a virtual machine view this is the same, as long as we don't hava parameter names as runtime. From a java syntax named parameters are to prefer.

Paulo Faria said...

How about double (double first, double second) ?

Anonymous said...

reducer takes a pair of doubles and returns a double. I don't see what's so difficult to understand about that. You Java monkeys are strange animals.

Anonymous said...

Actually I'd prefer:

{{double, double => double}, double => void} cumulate = {reducer, base =>
    ...
}


But maybe I'm just crazy. :)

eirikma said...

@paulo: a name for the closure is always needed, otherwise it would be impossible to refer to it and invoke it from the code. In my opinion that is exactly the same as a method name. In my opinion, there are two real differences between the suggested (implemented) closure declaration syntax and the existing method declaration syntax in java:

First, the method declaration syntax creates a visually clearer separation between input and output values of the function. To me, at least, it seems like the (braces-syntax) right-arrow is only related to the last value in the preceeding list. I know it's not, but it looks like it is.
Second, the method syntax uses names for the input parameters. I thought they used to be optional for interface/abstract method declarations in Java (i might be wrong here), but they seem to be mandatory now.
Actually, these names could be optional. You only need names for the parameters when you use (refer to) them: in code or in javadoc.

konberg said...

I'm curious about the separate enhancement you talk about: extension methods. Is that something you are ready to describe in detail?

Neal Gafter said...

@erikma: you wrote "@paulo: a name for the closure is always needed, otherwise it would be impossible to refer to it and invoke it from the code."

Actually, most closures do not need to be named in the code, any more than other subexpressions need to be named. (Imagine your argument applied to constants: "...a name for the string is always needed, otherwise it would be impossible to refer to it")

Anonymous said...

When I first learned of extension methods, I drooled.

You have all three of my Java Bug Parade votes for this feature!

Anonymous said...

I have a question about extension methods. Will defining an extension method allow the existing, modified class to implement a new interface?

Say we define Appendable, then an append extension method on ArrayList. Could we have ArrayList then implement Appendable via the extension method? Would this give us true mixins?

Paulo Faria said...

I guess that not having a name could blow up if you'd want to later add tuples. Parse this:
(double, double) (double first, double second)

Fatih Coşkun said...

@Jimenez: extension methods solve the problem of not being able to add new methods to released interfaces. For example take the List interface. With closures, it would be nice if List would have an each() method. This is not possible, because adding such a new method to a released interface would brake every class implementing it. Extension methods will "simulate" being new methods of old interfaces (with syntactic sugar) but in fact will be external static methods (for example).

Eugene Kuleshov said...

Neal, can you please clarify why for the following class, your compiler generates class signature: <H:Ljava/lang/Object;>Ljava/lang/Object;Ljavax/lang/function/IOOO<LA;[BTH;Lnull;>; ? Where "Lnull;" came from?

public class B<H> implements {A, byte[], H => int} {
  ...
}

Neal Gafter said...

@Eugene: that is the type argument to the function interface indicating the thrown exception types. "null" indicates that the function type doesn't throw any checked exceptions.

Axel Rauschmayer said...

Whatever the syntax I want closures... badly. Many other features can be simulated by code generation and are not missed as much (e.g.: multiple inheritance via generated delegating methods), but closures really dramatically improve the conciseness of the language and there is no "generate and forget" way around them.

As for the syntax: The closure result not coming first in the type result is a valid criticism. Has it been answered somewhere? Then you can just point people to that answer every time the issue comes up.

After looking again at the FCM proposal [1], I think I now get the BGGA syntax: I like the closure literal syntax (the FCM version is a little bit less appealing). I dislike the type signature syntax (here the FCM version looks nicer), but it really just mimics the literal syntax. So that does in fact lead to increased consistency.

But the FCM proposal allows one to refer to methods via statically type-checked literals, so methods become another source for closures. I still think this is a very desireable feature.

[1] "First-class methods (FCM)", Colebourne, Schulz. docs.google.com/View?docid=ddhp95vd_0f7mcns

Unknown said...

I've tried the following code:
{String, int => double} [] obj = {
{String x, int y => 1.0},
{String x, int y => 2.0}
};
but the compiler complained with the message "generic array creation". Where's the "generic" type there?

(Well, if I try
interface StringIntDouble {
double invoke(String x, int y);
}
and
StringIntDouble [] obj = {
{String x, int y => 1.0},
{String x, int y => 2.0}
};
it works correctly.)

Neal Gafter said...

@Edson: function types are specified to be translated into a generic interface.

Anonymous said...

I don't think bringing such a stuff to Java is a good idea and I also don't think Java really needs them. Java was always about simplicity and limited set of really needed features (no overloaded operators etc.) I'm affraid that code gets much less readable when closures are used/misused/overused.
I think every such feature should be REALLY CAREFULY examined before being added, because I don't think closures are something that would attract anyone except few geeks and in long-term it could damage the language itself.
Java should primarly focus on improving built-in APIs. I think Media support, animation and maybe 3D scenegraph is something that would be welcome and help to improve Java's position on desktop and better compete Adobe.

Mark Mahieu said...

Just a minor point. The current specification defines a ClosureLiteral as:

{ FormalParameters opt => BlockStatements opt Expression opt }

Assuming this is refering to the FormalParameters definition in the standard JLS (3rd ed.) grammar, I think this would mean that a closure parameter may optionally be marked as final and/or specify annotations, but the compiler doesn't appear to accept these.

Neal Gafter said...

Mark Mahieu: Good catch. That was a bug. It is fixed now.

JP said...

Neal,

Many examples won't run for me but only compile.I could succeed running very few examples.

But really it is gr8 fun learning closures.

Thanks very much for improving java language to next generation.


C:\closures\test\tools\javac\closures>javac -J-Xbootclasspath/p:c:/closures/lib/javac.jar -source 7 HelloWorldClosure.java

C:\closures\test\tools\javac\closures>java -Xbootclasspath/p:c:/closures/lib/javac.jar HelloWorldClosure
Exception in thread "main" java.lang.NoClassDefFoundError: javax/lang/function/OOO

Thanks
Prashant Jalasutram
http://prashantjalasutram.blogspot.com/

Fatih Coşkun said...

@anonymous:
You must think of closures to be a way of minimizing adding new language features in the future.

Take for example the new for loop added in Java5. That was an added new feature that could have been implemented as an API method, if closures were in Java5.

The stronger the abstraction of the base language is, the smaller is the size of additional language features. They can then be implemented in the API.

And I don't think closures will harm Java. Sure, some wont like closures and will certainly want to switch to another language. But to which one? All other mainstream languages have (or will have) closures, too.

Fatih Coşkun said...

Neal, when can we see some of the additional language features you are speaking of?

Mark Mahieu said...

Another very minor query, but it's based on something which surprised me a little - in the main() method below I have two similar statements, one using an anonymous class, the other using what I believe is the equivalent using closures.

Now, I had initially expected both of these statements to be rejected by the compiler with 'incompatible types' errors, but although the first one is, the closure version is actually accepted as valid, without even generating any warnings.

After reviewing the 'Closure Conversion' section of the current spec, I think I understand why it's allowed, as the compiler is checking the compatibility of the argument types - ie. assigning a raw List to a List<String> -
rather than my initial assumption that it would perform a compatibility check based on assignment to the Processor type as a whole (albeit with some additional conversion 'magic' thrown in).

Two questions though:
1) Could/should the specification be stricter in this regard, such that the closure conversion would be rejected in a manner consistent with the anonymous class assignment? Given that closures would all be new (ie Java 7) code, I don't think there would be be any code compatibility issues preventing enforcement of stricter assignment rules during closure conversion.
2) If the answer to (1) is no, then shouldn't it at least produce a warning?


import java.util.*;

public class MoreContrived {

    interface Processor<T> {
        void process(T o);
    }

    public static void main(String[] args) {

        // this statement is rejected by the compiler, as expected
        Processor<List<String>> p1 = new Processor<List>() { public void process(List list) {} };

        // this compiles successfully - but why no warning?
        Processor<List<String>> p2 = { List list => };

        // after all, I get an 'unchecked conversion' warning here...
        List<String> list = (List) null;
    }
}

Anonymous said...

@Fatih:

Well I think closures are something that doesn't fit Java's philosophy at all. This pretty functional oriented and looking strange. Every method in Java belongs to a class and functions are totally diffferent than methods. Together with properties (proposed for Java7) this would make Java7 not being Java any more.

I think for(:) statement is good, because is easy to understand for everyone and is just a shortcut for common Java construct.

I switched to Java several years ago from C++ and also tried C#. I missed overloaded operators, pointers and other stuff for a some time. But after experience in Java I have to say don't miss them and I even wouldn't like them to be in Java. Java is really about simplicity and I don't think more features means better language.

Want features that are not consistent with Java's philosophy? Choose other language and don't make current Java users to leave. That is my opinion.

Paulo Faria said...

I belive i dislike the prosed syntax.
It is inconsistent with the normal java syntax. I've been burned by generics before too.

Whats the reason for disallowing a syntax like this:
double methodThatAcceptsClosure( double closure() ){
return = closure() + 1.0D;

}

and for local closures with type inference :
double closure () = {
return 2.0D;
}

And just disallow nameless closures?

Neal Gafter said...

@paulo: first, Java has a separation between variable and method namespaces, and your suggestion attempts to mix them up. I don't know how to make that work, but perhaps it can be made to work. As for the rest of the syntax, see http://gafter.blogspot.com/2006/08/tennents-correspondence-principle-and.html and http://www.parleys.com/display/PARLEYS/Closures%20for%20Java

Anonymous said...

Something that would dovetail with language extensions very nicely is the ability to slap a conforming interface onto an existing class. It can be done right now with bytecode manipulation through custom classloaders but this is rather obscure. Are there any plans for that?

Fatih Coşkun said...

Neal, I have a question regarding extension methods and the 'for' keyword for declaring and invoking methods that are to be used with the control invocation syntax.

The extension methods will allow 'adding' an each() method to the List interface. This method should probably be declared using the for keyword. But how would such a method be invoked then? Would it look like the following?

myList.for each(T item) { ... }

Can't we just get rid of the 'for' keyword for the invocation of such methods? Why is it necessary for the invocation anyway? Isn't it enough to use the keyword for the declaration of the method?

Mark Mahieu said...

I'm very happy to see that serialization of closures works as I'd expect - ie. that a closure literal which references variables in an enclosing scope may be
serialized, deserialized and then invoked successfully, provided the closure is converted to a type implementing Serializable, and the types of the referenced
variables are also suitable for serialization.

I don't see any explicit mention of this in the specification however - is its absence intentional?

Unknown said...

@mark: you were able to serialize the closure? Could you maybe share how you did that? Because I can get it to compile but I only get NotSerializableException.

Unknown said...

>@Eugene: that is the type argument to the function interface indicating the thrown exception types.
> "null" indicates that the function type doesn't throw any checked exceptions.

Neal, why use null and not java.lang.RuntimeException here ?

Rémi

Unknown said...

Few words about the syntax:

@eirikma: "right-arrow is only related to the last value in the preceeding list."

In Ocaml you have:
{double * double => double}
which can be translated into:
{double, double => double}

So it isn't that weird.

As for "not beign consistent with Java": all parametr/variable declarations are of form:

{modifiers}* TYPE NAME

so declaration like:
double reducer(double,double)
is the inconsistent one, 'cause it mixes the parameter type with its name. If you also add parameter names, that would be really hard to read.

The syntax proposed by BGGA is short and informative. And making syntax shorter was one of the goals.

Mark Mahieu said...

quintesse: here's my test code - the first call to serialize() should succeed and the second should fail.


import java.io.*;

class SerializableTest {

    interface Foo extends Serializable {
        String getValue();
    }

    static class NonSerializable {
        String s = "Never see me";
    }

    public static void main(String[] args) throws Exception {

        String s = "Should see me";
        NonSerializable ns = new NonSerializable();

        serialize({ => s });
        serialize({ => ns.s });
    }

    static void serialize(Foo o) throws Exception {

        FileOutputStream fos = new FileOutputStream("p.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(o);
        oos.flush();

        FileInputStream fis = new FileInputStream("p.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        o = (Foo) ois.readObject();

        System.out.println("Read : " + o.getValue());
    }
}

Mark Mahieu said...

I ran into this while writing the serialization test - why does the conversion to Foo2 fail to compile?


class Contrived {

    interface Foo1 {
        String toText();
    }

    interface Foo2 {
        String toString();
    }

    Foo1 foo1 = { => "string" };
    Foo2 foo2 = { => "string" };
}

Neal Gafter said...

@Mark Mahieu: toString isn't abstract because an implementation is inherited from Object. Your interface Foo2 is therefore semantically equivalent to an interface that has no explicit members. If that's not ergonomically correct then the spec for the closure conversion will need to be changed.

Mark Mahieu said...

OK, that explanation makes sense - thanks.

On another topic, I'm getting errors referencing 'this' within my closures ('super' seems ok though). The following class compiles but I get 'Exception in thread "main" java.lang.NoSuchFieldError: this' when I attempt to run it:


public class ThisTest {
    
    public static void main(String[] args) {
        new ThisTest();
    }
    
    ThisTest() {
        { => this }.invoke();
    }    
}

Unknown said...

@mark: your code was pretty similar to mine, except that my interface Foo was defined like this:


interface Foo extends {int=>int}, Serializable {
}


any idea why my version doesn't work?

Neal Gafter said...

@Mark Mahieu: The bug you found with "this" has been fixed.

Mark Mahieu said...

@Quintesse: Your definition of Foo works for me. However it does fail if the closure refers to an instance variable rather than a local variable (if the containing instance is not Serializable) - is that the case?

@Neal Gafter: thinking about your answer to my toString() question a little more I'm now sure that I prefer the way it works currently, as I've realised the 'semantic equivalence' approach allows a closure to be provided where a Comparator is expected, which is a definite plus.

Unknown said...

I like your proposal. But "catching multiple exceptions" I'd rather like to see implemented with multi typed variables.

Neal Gafter said...

@Holger: You say you'd rather see multi-typed variables, but I don't know how that is different from what I would call disjunction types. What exactly do you mean?

Unknown said...

Maybe it is actually the same!? What I want is the general possibility to define fields, parameters and variables with multiple types. For example if I'm writing some code that deals with objects, that at the same time must implement Comparable, Serializable and Cloneable and as they are (implicitly) derived from Object, they have the methods hashCode and equals. With Java 6, I have no possibility to express this requirement in method signatures or anywhere else. I can only declare it as one of the required types and then cast it to the other types. If I use one of the interfaces, then I even have to cast the object to Object in order to access equals and hashCode.

Multityping would make my code typesafe. The caller of a method would be forced to pass in properly equipped objects, that implement all required types by the compiler. And in effect, this would result in less ClassCastExceptions at runtime.

Mark Mahieu said...

@Neal : Couple more issues. I feel like I'm spamming your blog with these - please say if there's somewhere else you'd like bugs reported.

Class Example1 below blows up the compiler with:

java.lang.AssertionError: unexpected type: { => null}
at com.sun.tools.javac.tree.TreeMaker.Type(TreeMaker.java:666)

public class Example1 {

    {
        { => { => null } }.invoke();
    }    
}


Example2 compiles but throws an IncompatibleClassChangeError at run-time.

public class Example2 {

    interface Chainable {
        Chainable call();
    }

    public static void main(String[] args) {

        Chainable c = { => { => null } };
        c.call().call();
    }
}

Example3 also compiles but throws a ClassCastException at run-time.

public class Example3 {

    interface Chainable extends { => Chainable } { }

    public static void main(String[] args) {

        Chainable c = { => { => null } };
        c.invoke().invoke();
    }
}

Neal Gafter said...

Holger: what you want can often be done using generics and conjunction types, also known as multiple bounds:

<T extends Serializable & Cloneable & Comparable<? super T>> whatever...

Neal Gafter said...

You're welcome to email your bugs to me directly: neal AT gafter DOT com.

Kevin Krouse said...

No mention of type inference? That is surprising.

Charles said...

Neal,
I'm interested to get your thoughts about how/if the closures proposal negates the need for Automatic Resource Management Blocks as proposed by Joshua Bloch here
Charles

Unknown said...

@Mark: you're right, if I change your example it works correctly, but I don't see why mine fails:


class Test {
void test() {
Foo add, add2;
int delta = 5;
add = {int x => x + delta};
ser(add);
add2 = deser();
}

private void ser(Foo x) { ... }
private Foo deser() { ... }
}

interface Foo extends {int=>int}, Serializable {
}


I can post the (de)ser methods as well but I can't imagine that they are the problem.

The exception is: java.io.NotSerializableException: Test

It's obviously not entirely clear to me yet how all this works exactly, so would anyone care to enlighten me why my version doesn't work? :)

PS: how do you maintain indenting while inserting code into your comment?

Fatih Coşkun said...

What do you think about the impact that closures will have on the number of class definitions in a project.

I may be wrong but wont every closure yield a new class definition? It is not only a new object instantiation, but a whole new class definition.

Wont this be horribly inefficient, if closures are used everywhere in the code?

Unknown said...

@fatih: I think it would be more or less the same because the most common alternatives for closures would probably involve introducing new interfaces and (inner/anonymous) classes anyway.

kodeninja said...

Can we please have named parameters in JDK 7???

Johannes said...

Is there any syntax planned for using existing methods directly as function objects? Ie some sort of 'method literal'?

It is a very common use case that you want to use an available method instead of an ad-hoc function literal.

At least the syntax overhead for function literals will decrease to a acceptable level but a 'method literal' syntax would be nice...

Example:
class Person{
@Description("The Name of the Person")
String getName();
String setName();
}

to extract all Names of a given set of Persons:

Person[] people;
names=map(Person.getName,people);

instead of

names=map({Person p=>p.getName()},people);

Consider even some possible meta magic:

Given the type of a 'method-literal' would be some subtype of FunctionX you could access metadata like annotations through this advanced structure. (ie a mix of java.lang.reflect.Method and FunctionX).

interface MethodX extends FunctionX{
<T> getAnnotation(Class<T> type);
//...
}

You could build some type of rich property like this (for use in GUI building,...) :

interface IRichProperty<ParentT,T>{
T get(ParentT parent);
void set(ParentT parent,T value);
String getDescription();
}

static <ParentT,T> IRichProperty richProperty(IMethod1<ParentT,T> getter,IMethod2<ParentT,T,Void> setter){
return new IRichProperty(){
T get(ParentT t){return getter.apply(t);}
String getDescription(){
getter.getAnnotation(Description.class).value();
}
// set skipped
};
}

In our example you could use this function like that:

prop=richProperty(Person.getName,Person.setName);

Something like that planned? Anyone else interested?

Mark Mahieu said...

@Quintesse: I think the key difference is that your closure is defined in an instance method - the compiler is then creating a class representing your closure which carries around a reference to that Test instance (just as though you'd defined an anonymous class there instead of the closure), and Test is not Serializable.

My closure was defined in a static method, so its class doesn't hold a reference to an instance of my SerializableTest class.

Oh, and I get indentation by replacing tabs with &nbsp;&nbsp;&nbsp;&nbsp; before pasting the code in.

Neal Gafter said...

I plan to stop capturing a reference to the enclosing instance when it isn't needed.

Unknown said...

Mark: you were right, making the method static made the serialization work. Thanks!

Axel Gross said...

Great stuff!

But I also agree with eirikma that the grouping of parameters is very not-Javaesc. Also curly braces usually denote method bodies which hasn't anything to do with parameters.
Hacked away with hugs ten years ago, so the syntax is not a problem for me...
...but guess what.. I have another suggestion which uses Java mindset ;)

Using a '#' to denote sth method-like (as used in Javadocs).

Variable Declaration
double #(double,double) reducer;

Cast
reducer = (double #(double,double)) obj;

Void return value
void #(double,double)

Any return value
? #(double,double)

Parameter (like in the original example)
void cumulate(double #(double,double) reducer, double base)

Pros:
* very concise
* mimics the layout of a method declaration
* works well without having to name it
* the name in a declaration is rightmost
should not be too hard to parse

Cons:
* don't know how that would be backwards * compatible, if that is a concern
* Any and void return parameters have a smell; also for parsing

I don't know if it would be feasable to get some reference to the method a closure was constructed from (or to name the instance in another way).
If it is we could easily express a constraint based on this:

Method name must be 'reduce':
void cumulate(double #reduce(double,double) reducer, double base)

Nominal type must be subtype of 'Reducer' (any member method)
double Reducer#(double,double) reducer

and combined
double Reducer#reduce(double,double) reducer

Note one could also add question marks to represent the 'missing' names (denoting some unknown value like in type capture), although I hardly doubt thats worth anything.
double ?#?(double,double) reducer
double ?#reduce(double,double) reducer

thanks for the attention, i'd really like to get some feedback (and/or a reference if i should post this somewhere else)

Axel

ps: using curly braces on a german keyboard layout is a real p***, so I'd be glad if there were not more of those ;)

Axel Gross said...

i forgot to define closure definition, so here it is:

* keep Javaesc syntax
* remove unneccessary clutter (optional return statement, inferred return and parameter types)

With inferred types:
int #(int,int) plus = #(x,y){ x+y };

for comparison the v0.5 Syntax:
{int,int=>int} plus = {int x, int y => x+y};

and the verbose way
int #(int,int) plus = int #(int x,int y){return x + y};

Guess the v0.5 would be easier to parse, but that functional style syntax 'embedded' in Java smells very much like mixing different languages (not blending them)

Neal Gafter said...

@Axel: your proposed syntax would be ambiguous if extended as you previously suggested to express thrown exception types. I don't understand why you would want to link type inference (which turns typechecking into an NP-complete problem) with the requirement to use "return" to yield a value from a closure.

Mark Mahieu said...

Maybe I'm missing something but I don't see any way for a function type to specify that it extends RestrictedFunction, so there's no way for the compiler to prevent me from passing a closure which uses non-final local variables or control transfer statements to a method accepting a function type parameter. For example, in the closures version of the fork-join framework, ParallelArray.combine() takes a {T,U=>V} as the combiner, and can/will invoke it on a different thread.

I'm grimacing at the fact that I'm about to use the S-word, but perhaps a slightly different syntax could be used to denote a function type extending RestrictedFunction - I don't really care what it is at this point, but for the sake of example something like {T,U==>V} in the above case?

Incidentally I'm only talking about the spec here - I know this doesn't apply to the prototype at this point.

Axel Gross said...

@Neil

Hm, the return statement should have been a => (read up your rationale and agree)

So the corresponding syntax should have been
int #(int,int) plus = int #(int x,int y){=> x + y};

About ambiguity in case of throws declarations;
the only case I could think of is usage as generic type parameter

Class <CType extends Closure, T2> ClosureStore<CType, T2>;

ClosureStore< #connect(URL) throws ServerDoesntExist, HTTP404, Date> myMethodStore;

was that what you meant? and the rationale to enclose the whole in brackets?
*sigh* the alternatives don't look too good then...

A: wrapping up the throws:
ClosureStore< #connect(URL) throws[ServerDoesntExist, HTTP404], Date> myMethodStore;

B: using another delimeter for throws:
ClosureStore< #connect(URL) throws ServerDoesntExist | HTTP404, Date> myMethodStore;

C: wrapping up the whole thing in brackets:
ClosureStore< {#connect(URL) throws ServerDoesntExist, HTTP404}, Date> myMethodStore;

{int #(int,int)} plus = {int #(int x,int y){=> x + y}};

D: wrapping up only in case there would be ambiguities:
ClosureStore< (#connect(URL) throws ServerDoesntExist, HTTP404), Date> myMethodStore;

int #(int,int) plus = int #(int x,int y){=> x + y};

A,B and are already becoming less Javaesque;
C is already getting too cluttered;

I'd still prefer B or D over the v0.5 syntax.
D adds clutter only when necessary; but it smells hard to figure out where those places for ambiguity are.


Concerning (static) inference like in
int #(int,int) plus = #(x,y){=> x + y};
I don't see the difficulty here (would be glad about a pointer).
Complexity should be the same as for the constructor type inference
you proposed.
In this case there is also not a difference between interface/class, so there is no need to differentiate.

In v0.5-like syntax that would be
{int,int=>int} plus = {x,y => x+y};

Neal Gafter said...

Axel: inferring the types of closure arguments is NP-complete. See http://blogs.msdn.com/ericlippert/archive/2007/03/28/lambda-expressions-vs-anonymous-methods-part-five.aspx for a neat proof.

Using another syntax, for example, '=>' for returning from a closure, doesn't solve the problem, for exactly the same reason that using 'return' doesn't work: the meaning of that syntax would be shadowed by a nested closure.

Mark Mahieu said...

These disjunctive exception types are really handy! How feasible would it be to allow them to be used in type bounds?

As an example, say I have a kind of Builder class:

    class Builder<throws X extends Exception> {
        static <X1> Builder<X1> create({ => void throws X1 } command) { ... }
        Builder<X> add({ => void throws X } command) { ... }
        void execute() throws X { ... };
    }

what I'd really like to be able to write is something like:

    try {
        Builder.create({ => if (...) throw new ProblemException(); })
            .add({ => if (...) throw new EmergencyException(); })
            .add({ => if (...) throw new DisasterException(); })
            .execute();
    }
    catch (ProblemException|EmergencyException ex) {
        // DisasterExceptions are propogated up
    }

But for that to work I think I'd need to be able to write my add() method something like:

    <X2 extends Exception, X3 extends X|X2> Builder<X3> add({ => void throws X2} command) { ... }

Feasible or have I lost the plot?

Neal Gafter said...

Mahieu: I don't think that's hard at all.

Victor Toni said...

Somehow I would like to see "," used as the separator when catching multiple exception (I believe to have seen it used in Python). "|" might be semantically nearer to the intended meaning but actually it's more an XOR.
This is a feature I am expecting eagerly...

BTW is there any thought about introducing complex numbers and/or some kind of structure type?

Axel Gross said...

@Neil I wanted to make a lesser type of inference than the one you provided the pointer to. There the problem was to infer the arguments from the closure body.
What I suggest is to take the types from the outer context. This is a different problem and just a small one.

So, if a closure would be used as parameter or variable, the type of the closure would be the declared one. The closure variables and return type would be defined from that.

so if there was a method
int apply(int in, {int,int->int} m){..}

you could use it like
apply( 1, {x,y -> x*y} )
instead of
apply( 1, {int x,int y -> x*y} )
which would be neat

The following would produce a compile error
int apply2(int in, {Object->int} m){..}
apply2( 2, {x -> x*x} )
as the * is not defined for Object (same for invoking methods on x)

When nesting closures there should be no problem as every level would have to be declared
{int -> {int -> int} } mulWith = {x -> {y -> x*y} };

If a more general declaration is needed, the details have to be put on the 'implementation' site
{int -> Object } mulWith = {x -> {int y -> x*y} };

In case of overloaded methods the programmer would still have to declare the right type.
void a( {float->float} m){..}
void a( {int->int} m){..}
a( { int i-> i*2 } );

else there should be compile errors.

So this suggestion would make a IHMHO very common case better write-/readable.

Neal Gafter said...

@Axel: This is the kind of inference C# has. It is NP-complete because of overload resolution.

eirikma said...

@Neal: As far as I understand, Scala has type inference working for "most cases", though sometimes the compiler says "inference too difficult, please specify the type" (or something like that - I haven't tried it). Type inference would be very useful, even if it only solves only 98% of the cases. I guess that, being NP-complete for overloaded methods, the compiler could detect if n is too large (above 5 or something) when such methods are involved, and flag the "too complicated" error.

Axel Gross said...

@Neil I suggested not to infer in presence of overloaded methods (thus no NP-completeness) and demand the typing to be done by the programmer. For the more regular case (method not overloaded) it would clean up the code.

Anonymous said...

Neil, I successfully compile and run many examples. However, using com.sun.tools.javac.Main() (from jdk1.6) to compile always fails. The args being passed to compile are:
[-J, -Xbootclasspath/p:C:\java\closures-2008-08-11\lib\closures.jar, -cp, C:\java\closures-2008-08-11\lib\closures.jar;C:\j24homedev\lib\j24Core.jar, -sourcepath,., -d,.,TEST_SELECT_cl.java]

I can compile this file using the wrapped javac.bat...Are closures supposed to work with com.sun.tools.javac.Main()? The reason I need to use this is we are generating java code (now with closures). I suppose I can use Runtime.exec(), but I'd rather not fork a process. As well, when do you predict jdk1.7 will be available (with closures)?

Thanks,
Rick

Neal Gafter said...

@Rick: I'm sorry, I don't know what the problem is. I suspect you're missing some environment variable that is set by the javac launcher.

Closures are almost certainly not going to be part of Java 7 when it ships in a year or so.

Post a Comment