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