Project Lambda: Java Language Specification draft
Rémi Forax
forax at univ-mlv.fr
Fri Jan 22 18:24:22 PST 2010
Hi Neal,
Le 23/01/2010 02:36, Neal Gafter a écrit :
> A couple of thoughts on this draft, inline
>
> On Fri, Jan 22, 2010 at 2:55 PM, Alex Buckley<Alex.Buckley at sun.com> wrote:
>
>> This document does not consider implementation. The mapping of lambda
>> expressions to objects, and of function types to class or interface
>> types, is neither designed nor specified. Even if the mapping was
>> designed here, it is unlikely ever to be specified in the JLS. Binary
>> compatibility for lambda expressions will eventually be specified in
>> terms of changes to function types only. It is a goal of this document
>> to allow the implementer freedom as to how and when lambda expressions
>> are evaluated.
>>
> I think these are unavoidable in the JLS. The specification must be
> precise enough that code generated by distinct Java compilers can
> interoperate. Within the specification, we need to know if a class
> can "implement" a function type. If so, it is probably an interface.
> If not, it probably isn't.
>
>
>> - Lambda expressions as closures: There are effectively-final
>> variables, but I am holding off shared variables for now. As
>> background reading to why loop variables should not be shared, see
>> http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx.
>>
> Actually, that is largely an error in the specification of C#'s
> foreach loop, which we're aiming to fix.
>
>
>> * Expressions
>>
>> [15.8 Primary Expressions]
>>
>> Expression:
>> LambdaExpression
>>
>> LambdaExpression:
>> '#' '(' FormalParameterList_opt ')' '(' Expression_opt ')'
>> '#' '(' FormalParameterList_opt ')' Block
>>
>> #()()
>> #()(5)
>> #()(x.m())
>> #()((foo++))
>> #()("a"+"b")
>> #()( {1,2,3} ) // Proposed collection literal expression from Coin
>>
>> #(){}
>> #(){return 5;}
>> #(){x.m();}
>> #(){foo++;}
>>
> Did you mean to make these primary expressions? I hope so. Otherwise
> many uses would require yet another set of parens.
>
>
>> [15.8.3 this]
>>
>> The keyword this may be used only in the body of an instance method,
>> instance initializer or constructor, or in the initializer of an
>> instance variable of a class, *or in a lambda expression*.
>>
>> The type of this in a lambda expression is the function type of the
>> lambda expression.
>>
>> /*
>> Treatment of 'this' inside a lambda expression is essentially the same
>> as 'this' inside the body of an anonymous inner class.
>> */
>>
> That's the worst possible treatment of "this". It has all the
> disadvantages of the inner class approach (not being transparent) with
> none of the advantages (you can't use any inherited members of the
> type to which the lambda expression is converted). This is the first
> time I've heard anyone advocate such treatment for "this".
>
Agree.
Another question is what is the meaning of super in that case ?
>
>> [15.8.6 Lambda Expressions]
>>
>> A lambda expression is used to create a new object that is a lambda
>> instance (15.8.7).
>>
> I don't think you mean to say "new" object. I think you mean to say
> that it results in an object that is a lambda instance. Whether or
> not it is new is something you explicitly said you didn't want to
> specify.
>
Yes, this is important.
>
>> A lambda expression specifies a expression or block of code,
>>
> That's not quite right, because it could be neither (the expression is
> optional).
>
I think that this is an error in the grammar,
#() () seems not really useful.
>
>> followed
>> by a (possibly empty) list of formal parameters to the expression or
>> block.
>>
> Actually, in your syntax the parameters *precede* the body, not follow it.
>
>
>> The body of a lambda expression is an expression or a block of code.
>>
> Again, sometimes it is neither.
>
>
>> If the body of a lambda expression is an expression, then the type of
>> the body is the type of the expression.
>>
> And if it is neither?
>
>
>> If the body of a lambda expression is a block, then either all or none
>> of the return statements in the block must have an Expression. If no
>> return statement has an Expression, then the body of the lambda
>> expression is void, i.e. has no type.
>>
> That's inconsistent with the way methods work, and would prevent the
> following useful code
>
> Callable<String> lazyResult = #(){ throw new UnsupportedOperationException(); }
>
Why not using the left hand side type, like when infering a generic call ?
I think it will having to introduce 'Nothing'.
>
>> If all return statements have an
>> Expression, then the types of the Expressions must be
>> assignment-compatible with each other, or a compile-time error
>> occurs.
>>
> This disallows "if (e) return "foo" else return new Object();" because
> Object is not assignment-compatible to String.
>
>
>> The type of the body is lub(T1..Tn) where T1..Tn are the types
>> of the Expressions after boxing conversion.
>>
> So the result type is always a reference type (or void) when the body
> is a block?
>
Yes, it should be after a possible conversion and
the conversion should be done or not depending on proto-type.
>
>> The type of a lambda expression is a function type #T(S1..Sm)(E1..En)
>>
> That syntax is not defined anywhere in your specification. What is a
> function type?
>
This is defined in section 4.3
>
>> where:
>>
>> - If the body of the lambda expression is void, then T is void,
>> indicating no return type; otherwise, T is the type of the body of
>> the lambda expression after capture conversion.
>>
>> - S1..Sm is the list, possibly empty, of types of the formal
>> parameters of the lambda expression.
>>
>> #()() has type #void()
>>
> According to the specification above, this is missing some parens.
>
>
>> #() { if (..) return "1"; else return 2; } has type #Integer()
>>
> It looks like an error because of the constraint "If all return
> statements have an
> Expression, then the types of the Expressions must be
> assignment-compatible with each other, or a compile-time error
> occurs."
>
>
>> Any local variable, formal method parameter, or exception handler
>> parameter used but not declared in a lambda expression must be
>> effectively-final.
>>
>> A local variable, formal method parameter, or exception handler
>> parameter is effectively-final if it is never the target of an
>> initialization or assignment expression except where definitely
>> unassigned.
>>
> In other words, can't capture much more than an anonymous inner class
> could. Disappointing.
>
>
>> It is a compile-time error to modify the value of an effectively-final
>> variable in the body of a lambda expression.
>>
> Given the definition of effectively-final, above, this constraint is vacuous.
>
>
>> FunctionType:
>> '#' ResultType '(' [Type] ')' FunctionThrows_opt
>>
>> FunctionThrows:
>> '(' 'throws' ExceptionTypeList ')'
>>
>> ExceptionTypeList:
>> Identifier
>> ExceptionTypeList '|' Identifier
>>
>> The notation #T(S1..Sm)(E1..En) indicates a function type with return
>> type T, formal parameter types S1, S2, ..., Sm, and checked exception
>> types E1, E2, ..., En.
>>
> I think you're missing "throws". Either that, or you haven't told us
> the relationship between this syntax and the syntax for function
> types.
>
>
>> 'void' may be used in a function type to indicate that the body of a
>> lambda expression has no return value. This occurs if the body of the
>> lambda expression is a block that can either a) complete normally or
>> b) complete abruptly by reason other than being a return with value V.
>>
> What about the case when there's no expression or block? e.g. #()()
>
>
>> [4.10.4 Subtyping among Function Types]
>>
>> #T(S1..Sm)(E1..En) is a direct supertype of #V(U1..Um)(F1..Fo) iff all
>> of the following hold:
>> - T is a supertype of V.
>> - for i in 1..m: Ui is a supertype of Si.
>> - for j in 1..o: There exists a k in 1..n such that Ek is a supertype of Fj.
>>
>> #Object(String,Integer) is a supertype of #Package(Object,Number).
>> #Object(Object,Object) is also a supertype of #Package(Object,Number).
>> #Object(Object[]) is a supertype of #Object[](Object).
>>
> So "#float()" can be assigned from "#int()"? It will be interesting
> to see how one can generate verifiable code for this without resorting
> to some reflection-like APIs. Similarly, I'm surprised you allow
> assigning "#void(float)" to "#void(int)". How will the generated code
> for the lambda know how to interpret the bits of the incoming value?
>
I think this can of conversion can be handled by method handle
but these rules are aliens to Java.
>
>> Object is a direct supertype of any function type.
>>
>> A function type that is void (i.e. has no return type) and has formal
>> parameter types P1..Pn is a supertype of a function type #T(S1..Sn)
>> iff Si is a supertype of Pi (i in 1..n).
>>
> So "#void()" can be assigned from #int()"? It will be interesting to
> see how to generate verifiable JVM code for calling these (how much is
> left on the stack by invoking a lambda?).
>
>
>> The above [SAM] definition deliberately does not treat multiple non-Object
>> abstract methods with compatible signatures as if they represented a
>> single abstract method. This reflects existing practice whereby if an
>> interface or abstract class has multiple such members, it is not
>> possible for a non-abstract class to implement the interface/extend
>> the abstract class simply by providing a single concrete method.
>>
> That's not existing practice:
>
> interface A { void f(); }
> interface B { void f(); }
> interface C extends A, B {} // not SAM by definition
> public class D implements C { public void f() {} }
>
I could be very useful if you want to retrofit C
that only extends A by introducing extends B.
Without that rule, this kind of change will break source
compatibility.
>
>> A lambda conversion exists from a function type #T(S1..Sm)(E1..En) to
>> the descriptor of the target abstract method M of a SAM type, provided
>> that all of the following hold:
>>
>> - If T is not void, then T can be converted to the return type of M by
>> assignment conversion.
>> - If T is void, then M is void or has return type java.lang.Void.
>> - M is not generic and has m formal parameters.
>> - For i in 1..m, the i'th formal parameter of M has type Si.
>> - For j in 1..n, the checked exception type Ej is a subtype of some
>> exception type in the throws clause of M.
>>
> + The constructor is accessible?
>
> Where in the caller are exceptions that were declared in the throws
> clause of the SAM's constructor checked?
>
> You need to specify the runtime behavior of this conversion (when the
> constructor is invoked is observable).
>
>
>> The type of this in a lambda expression is the function type of the
>> lambda expression.
>>
> Oh, really? So the result type of a lambda expression can depend on
> the type of "this" (if "this" appears within a return statement). And
> the type of "this" depends on the result type of the lambda
> expression. Your specification gives no hint how this infinite
> regress is to be resolved.
>
Is there a cool Hindley-Milner like algorithm somewhere ?
>
>> Therefore, it is convenient for the body of a lambda expression to
>> have access to members of the SAM type. To achieve this, I am thinking
>> that 'this' in the body of the lambda expression may be cast to the
>> SAM type:
>>
> It gets worse and worse! I thought you were trying to avoid
> specifying implementation details, but this would force the objects to
> be the same. I cannot imagine how you could make that happen without
> introducing some significant inefficiencies. Consider:
>
> abstract class R { public abstract void run(); }
> #void() lambda = #() { R self = (R)this; ... }
> R r = lambda; // magic!
>
> Note that the object referenced by the variable r is of type "R", but
> it is also of the reference type "#void()" (it is visible with that
> static type as "this" inside the body of the lambda). Therefore, one
> could convert lambdas from one "SAM" to a different compatible "SAM"
> using function types as intermediaries:
>
> abstract class R1 { public abstract void run(); }
> abstract class R2 { public abstract void invoke(); }
> #void() lambda = #() {}
> R1 r1 = lambda;
> R2 r2 = (#void()) r1; // magic!
>
> Note that the above cast must succeed, because the object r1 must
> dynamically be a subtype of "#void()" - otherwise, it would not be
> capable of being viewed as that type when seen as "this" inside the
> lambda.
>
> Also, this will encourage people to write casts (that might be incorrect).
>
>
>> [5.3] Method Invocation Conversion
>>
>> Method invocation contexts allow the use of one of the following:
>> - a lambda conversion (5.1.14).
>>
> Interesting. How does type inference and overload resolution work
> when calling an overloaded method with an argument that is a lambda
> expression? Type inference currently does not have any rules to
> handle these cases.
>
> For example, if I have a generic method
>
> <T> T doit(#T() lambda) { return lambda!(); }
>
> And an invocation
>
> String s = doit(#()("foo"));
>
> There doesn't appear to be any way to infer that the type argument to
> the invocation is String. More subtly, with
>
> <T extends Runnable> void doit2(T t) {}
>
> How does one infer the type parameter T in the invocation
>
> doit2(#(){});
>
>
>> A lambda invocation expression on a lambda expression of type
>> #T(S1..Sm)(X1..Xn) can throw an exception type E iff either:
>>
>> - some expression of the argument list can throw E, or
>> - there exists an i in 1..n such that Xi is E.
>>
> + or the receiver expression can throw E?
>
>
Rémi
More information about the lambda-dev
mailing list