The symbolic, non-executable form of a method handle's invocation semantics. It consists of a series of names. The first N (N=arity) names are parameters, while any remaining names are temporary values. Each temporary specifies the application of a function to some arguments. The functions are method handles, while the arguments are mixes of constant values and local names. The result of the lambda is defined as one of the names, often the last one.
Here is an approximate grammar:
{@code}LambdaForm = "(" ArgName* ")=> " TempName* Result "}" ArgName = "a" N ":" T TempName = "t" N ":" T "=" Function "(" Argument* ");" Function = ConstantValue Argument = NameRef | ConstantValue Result = NameRef | "void" NameRef = "a" N | "t" N N = (any whole number) T = "L" | "I" | "J" | "F" | "D" | "V" }
Names are numbered consecutively from left to right starting at zero. (The letters are merely a taste of syntax sugar.) Thus, the first temporary (if any) is always numbered N (where N=arity). Every occurrence of a name reference in an argument list must refer to a name previously defined within the same lambda. A lambda has a void result if and only if its result index is -1. If a temporary has the type "V", it cannot be the subject of a NameRef, even though possesses a number. Note that all reference types are erased to "L", which stands for {@code Object}. All subword types (boolean, byte, short, char) are erased to "I" which is {@code int}. The other types stand for the usual primitive types.
Function invocation closely follows the static rules of the Java verifier. Arguments and return values must exactly match when their "Name" types are considered. Conversions are allowed only if they do not change the erased type.
- L = Object: casts are used freely to convert into and out of reference types
- I = int: subword types are forcibly narrowed when passed as arguments (see {@code explicitCastArguments})
- J = long: no implicit conversions
- F = float: no implicit conversions
- D = double: no implicit conversions
- V = void: a function result may be void if and only if its Name is of type "V"
Although implicit conversions are not allowed, explicit ones can easily be encoded by using temporary expressions which call type-transformed identity functions.
Examples:
{@code}(a0:J)=> a0 } == identity(long) (a0:I)=>{ t1:V = System.out#println(a0); void } == System.out#println(int) (a0:L)=>{ t1:V = System.out#println(a0); a0 } == identity, with printing side-effect (a0:L, a1:L)=>{ t2:L = BoundMethodHandle#argument(a0); t3:L = BoundMethodHandle#target(a0); t4:L = MethodHandle#invoke(t3, t2, a1); t4 } == general invoker for unary insertArgument combination (a0:L, a1:L)=>{ t2:L = FilterMethodHandle#filter(a0); t3:L = MethodHandle#invoke(t2, a1); t4:L = FilterMethodHandle#target(a0); t5:L = MethodHandle#invoke(t4, t3); t5 } == general invoker for unary filterArgument combination (a0:L, a1:L)=>{ ...(same as previous example)... t5:L = MethodHandle#invoke(t4, t3, a1); t5 } == general invoker for unary/unary foldArgument combination (a0:L, a1:I)=>{ t2:I = identity(long).asType((int)->long)(a1); t2 } == invoker for identity method handle which performs i2l (a0:L, a1:L)=>{ t2:L = BoundMethodHandle#argument(a0); t3:L = Class#cast(t2,a1); t3 } == invoker for identity method handle which performs cast }
@author John Rose, JSR 292 EG