Stream proposal and null results
Scott Carey
scottcarey at apache.org
Thu May 17 15:10:49 PDT 2012
On 5/11/12 10:55 AM, "Brian Goetz" <brian.goetz at oracle.com> wrote:
>Yes, you caught us :)
>
>Currently getFirst() is the only method that cheats in this way (using
>null as a sentinel), and this is generally a step in the wrong
>direction. A much more promising direction here would be to do
>something like Option (Guava) or Optional (Scala) where getFirst returns
>an Option, rendering it impossible for the user to "forget" to do the
>null check.
There are discussions in Scala-land about the performance problems with
Option[A]. What is frustrating is that the object wrapper is not
necessary for correctness as long as Some(null) is not allowed -- it is
simply a mismatch between the type system and runtime representation.
Some have tried to have the compiler never materialize the wrapper object.
Others have suggested that union types in the future would naturally
allow the compiler to do all the work and never materialize such a thing
at runtime.
I suggest that Java leap-frog this issue and not build an object that is a
wrapper for an optional value, and somehow let the type system hoist a
static method into what looks like a method call on the object
syntactically but is null-safe.
Imagine if the following was possible with defender methods:
---------------
interface Optional<T> {
final T getOrElse(T default) {
Objects.getOrElse(this, default);
}
---------------
class Objects { ...
public static <T> getOrElse(T object, T default) {
if(null == object) {
return default;
} else {
return object;
}
----------------
And similar for the
T getOrElse(Callable<T>)
and
void getOrElse(Runnable)
variants.
Then, no materialization of an option type is needed -- if
java.lang.Integer
implements Optional<Integer>
Then it pushes the null-safe static method from Objects into syntactic
sugar for Integer as a null-safe method call on an Integer object:
Integer nothing = null;
nothing.getOrElse(3); // <-- no NPE! it is just syntactic sugar for
Objects.getOrElse(nothing, 3);
In short, I think there are creative ways to push this into the type
system and not require object boxing. There would be other uses for this
besides Option. Many other uses. I think this works as long as the
defender method is final and naming collisions have a good solution --
this must be invokestatic and never resolve to invokevirtual or
invokeinterface since the object may not exist. Maybe there is something
creative with invokedynamic that is possible.
Other methods that I would think go on Optional:
map and flatMap.
>And, of course, having the return type explicitly represent
>maybe-null is more self-documenting than the current prototyped
>approach. Moving to some sort of Option-like approach here is on our
>to-do list, but currently we're working through some bigger issues
>first, such as how the streams framework attaches to collections.
>
>A secondary benefit of this approach is that it eliminates a "garbage"
>variable from the client code. Such "garbage" variables can often
>obfuscate the flow of what's going on.
>
>So, total agreement; we just haven't gotten around to this yet.
>
>
>
>On 5/11/2012 1:06 PM, Scott Carey wrote:
>> In the stream API draft proposal, I noticed that null was used to
>>signify
>> a failed computation or filter:
>>
>> Method matching =
>>
>>Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
>> .filter(m -> Objects.equals(m.getName(),
>>enclosingInfo.getName())
>> .filter(m -> Arrays.equals(m.getParameterTypes(),
>> parameterClasses))
>> .filter(m -> Objects.equals(m.getReturnType(), returnType))
>> .getFirst();
>> if (matching == null)
>> throw new InternalError("Enclosing method not found");
>> return matching;
>>
>>
>> It would be very useful to avoid requiring clients to check the result
>>for
>> null. This error prone null check pattern can be avoided and pushed into
>> the framework with something like:
>>
>>
>> return
>> Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
>> .filter(m -> Objects.equals(m.getName(),
>>enclosingInfo.getName())
>> .filter(m -> Arrays.equals(m.getParameterTypes(),
>> parameterClasses))
>> .filter(m -> Objects.equals(m.getReturnType(), returnType))
>> .getFirstOrElse(() -> throw new InternalError("Enclosing
>>method
>> not found"));
>>
>>
>> A couple other signatures for the last line are very useful as well:
>>
>> .getFirstOrElse(() -> someDefaultComputation()));
>>
>>
>> .getFirstOrElse(someDefaultValue));
>>
>>
>>
>>
>> If the only API contract for a computation with no result is to return
>> null, then the pattern
>>
>> T tempVar = values.someComputations().getResult();
>> if (null == tempVar) {
>> // do something about no result here
>> }
>> return tempVar;
>>
>> will be extremely common boilerplate that can be avoided .
>>
>> I think there are a three signatures that together remove the
>>boilerplate.
>> Below I have changed the name from getFirstOrElse to getFirst:
>>
>> void getFirst(Runnable elseAction)
>>
>> T getFirst(Callable<T> elseResult)
>> T getFirst(T elseVal)
>>
>> T getFirst() is then simply a shortcut for
>>
>> T getFirst((T)null);
>>
>>
>>
>>
>> There is likely something more elegant than multiplying the number of
>> method signatures for every method that has an optional result.
>>
>>
>>
More information about the lambda-dev
mailing list