Stream proposal and null results
Scott Carey
scottcarey at apache.org
Thu May 17 17:35:41 PDT 2012
On 5/17/12 3:27 PM, "Brian Goetz" <brian.goetz at oracle.com> wrote:
>Your Scala-bias is showing when you say "push it into the type system",
>like that is a good thing :)
Scala folk say I'm Java-biased.
Lets re-phrase: "I want the compiler to help users prevent mistakes with
results that may be null". Or "there should be a better Java convention
than wrapping every result with a temporary variable and null check".
Adding extra operators like the once proposed '.?' is not a general
solution ( what about getOrElse(Callable) or transform/map ?).
I rarely code with Scala, I suppose I am 'biased' because spent time on
mailing lists on several JVM languages, and have experimented with some
prototypes in several. Java is still my day-job. Currently, the Lambda
project is very interesting to me because it could solve several design
issues in Apache Avro representing an API that implements functions to be
composed together dynamically for dynamically flexible serialization.
I'm not suggesting Java get all scala-complicated. Having the compiler
help prevent user errors with null is not that far away from having the
compiler know that something is an Integer and not a Long. Its not a
suggestion for something like higher-kinded types or union types.
>
>Some perspective: some have suggested that this problem isn't even big
>enough for a new library class like Optional -- that using null as a
>sentinel is good enough. So if this problem is not big enough for even
>one new library abstraction, it is certainly not big enough for a new
>language feature!
I agree that a library class like Optional or Option is not desirable. My
view is that an Option/Optional object is a hack with bad performance
characteristics that causes almost as many problems as it solves (does
that still sound Scala biased?). You can use Google Guava or write your
own if you want it -- that uses Java's current type system features to
provide users with some null safety, at the expense of memory,
performance.
Rather than do that, I am suggesting that such an object (in the language
library or in Google Guava) is not necessary if the language had better
ways to deal with using an object that may be null.
Having written much code and libraries that have used null sentinels in
Java and dealing with the downstream consequences later, I hope for some
progress here.
I am not suggesting that returning a null sentinel is a bad idea. I am
suggesting that there is a need for a better way to operate on a
potentially null result than write a conditional statement and keep a
temporary variable or resort to boxing it in something like Option. It
seems natural that Lambda relates to this since it allows one to easily
build compositions of functions, and some of those functions will return
possibly-null results.
>
>What you are asking for isn't exactly static extension methods (since
>you wouldn't want to inject getOrElse unconditionally into Object, but
>only when viewing an Object as an Optional)
I suppose injecting it into Object is possible (if namespace collision is
somehow dealt with). My proposal is not the only way to deal with this.
>and isn't exactly interface
>injection (because you'd get fouled up by the fact that your receiver is
>null), but more a swizzling of the two.
I don't know enough about the guts of the JVM or the lingo to label it
appropriately. I also have no idea how challenging it is to do. Perhaps
there are major issues that make it a non-starter. Perhaps it is a minor
change from the current defender methods. I simply hope that the idea is
considered as an alternative to introducing something like Option, that is
potentially more general purpose and flexible.
It would be very useful (and have good performance characteristics) if API
designers had the ability to make a static method with the right type
signature appear to syntactically be used on the return from a method
call. Perhaps this idea helps someone else come up with a better idea.
Maybe having the static methods on Objects is decent for now:
return
Objects.orElse(Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredM
ethods())
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
.filter(m -> Arrays.equals(m.getParameterTypes(),parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.getFirst(),
() -> throw new InternalError("Enclosing method not found"));
That is hard to follow because you can't chain static method calls.
The below is easier to follow:
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))
.getFirst()
.orElse(() -> throw new InternalError("Enclosing method not found")); //
a static method, somehow made chainable from the result of getFirst()
or even:
return enclosingInfo.getEnclosingClass().getDeclaredMethods()
.asList() // may not be necessary if filter or a toStream() is added to
Object[] ?
.filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
.filter(m -> Arrays.equals(m.getParameterTypes(),parameterClasses))
.filter(m -> Objects.equals(m.getReturnType(), returnType))
.getFirst()
.orElse(() -> throw new InternalError("Enclosing method not found"));
>
>
>
>On 5/17/2012 6:10 PM, Scott Carey wrote:
>>
>> 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