Function type naming conventions
Brian Goetz
brian.goetz at oracle.com
Wed Jan 23 12:11:06 PST 2013
Returning to this....
Recall that the primary goal in choosing SAM names is user ergonomics.
Not only do we want to make things clear, but we want to assign the
simple names to the most commonly used configurations. Hence
"Function<T,U>" instead of "ObjectToObjectFunction". A secondary goal
is for the names to be mechanically derivable from a reasonably small
set of rules.
To recap where we are:
Base types, each with their own natural arity:
Block<T>
T -> void
Function<T,R> T -> R
Predicate<T> T -> boolean
Supplier<T> () -> T
Plus some derived convenience types:
UnaryOperator<T> extends Function<T,T>
BinaryOperator<T> extends BiFunction<T,T,T>
Plus arity prefixes to modify the natural arity of these:
BiFunction<T,U,R> // (T,U) -> R
BiPredicate<T,U> // (T,U) -> boolean
We have a primitive specialization convention that lets you prefix
{Int,Long,Double,Boolean,Obj} on front of one of these (possibly
multiple times). In accordance with our ergonomics goal, we want simple
specializations for those that specalize return type, since that has
been the most common specialization. (Obj is used when you want to
specialize later type params.)
We first tried ordering the type parameters return-type-first, so that
partial specialization could proceed left-to-right. This went over like
a lead balloon. So we then adopted the rule that we specialize return
type first when the return type is generic, and otherwise left-to-right.
This means that
IntFunction<T>
is a function T -> int. This seemed attractive as T -> int was far, far
more common than int -> T.
But this special treatment introduces anomalies. For example, this rule
would (probably) assign
IntDoubleFunction
for double -> int, whereas users will probably expect IntDouble function
to correspond to int -> double based on left-to-right. There are ways
to further complicate the mapping to avoid this but that just moves the
anomalies into darker corners.
Based on offline discussions with Kevin and Joe, we concluded that
backing off slightly from our desire to have the shortest possible name
for the most common specialization can yield a more consistent naming
scheme while still being acceptably ergonomic.
So, new proposal:
- When specializing return type, prefix "ToXxx"
- Otherwise specializations gobble type arguments left-to-right.
So:
Function<T,U> // T -> U
DoubleFunction<T> // double -> T
ToIntFunction<T> // T -> int
DoubleToIntFunction // double -> int
This also means there is more than one way to represent the same thing
in some cases. For example, also consistent with these rules are:
DoubleIntFunction // double -> int
since this covers all the type arguments.
Only Function and Supplier are generic in return, so specializations of
Block and Predicate would not be affected by the new "To" rule.
This scheme is simpler and has fewer anomalies. The casualty is that
IntFunction becomes ToIntFunction -- arguably clearer, and only slightly
more verbose -- overall seems a win.
Here's how the current SAMs would be affected:
IntFunction<T>
ToIntFunction<T>
IntBiFunction<T,U>
ToIntBiFunction<T,U>
IntBlock
not affected
IntBinaryOperator
not affected
IntPredicate
not affected
IntSupplier
ToIntSupplier
alternately, not affected
IntUnaryOperator
not affected
ObjIntBiBlock
not affected
ObjIntFunction
IntToObjFunction
alternately, IntObjFunction
A possible (orthogonal) tweak to this system would be:
- When there are enough specializations to cover all arguments, omit
the arity prefix
This would turn ObjIntBiBlock into ObjIntBlock. Some more investigation
would be required to ensure there are no collisions here.
More information about the lambda-libs-spec-observers
mailing list