Stream proposal and null results

Brian Goetz brian.goetz at oracle.com
Fri May 18 07:13:33 PDT 2012


Good that this option is open to third-party developers even if we don't 
handle it now.  (The design of Fillable<T> is still in flux, so stay 
tuned.)

The higher-level goal is to recognize that:
  - Some streams are known to the programmer to return exactly one 
element (Guava has an Iterators.getOnlyElement which enforces this)
  - Some streams are known to the programmer to return no more than one 
element

and give developers tools for capturing this in their code, so they can 
detect invariant failures easily.

On 5/18/2012 4:28 AM, Peter Levart wrote:
>
> On Thursday, May 17, 2012 07:09:32 PM Brian Goetz wrote:
>>
>> In the context of the discussion we're having, which is whether to use
>> Optional<T>  as the return type for Stream<T>.getFirst, this is not an
>> issue, because the option-ness is likely to be hidden by the chaining:
>>
>>     String s = people.filter(p ->  p.age()>  18)
>>                      .map(Person::getName)
>>                      .getFirst()
>>                      .getOrThrow(() ->  new NoSuchPersonException());
>
> Even if you don't commit to have Option<T>  in the standard library, 3rd party libraries could interface with streams using .into(Fillable). For example:
>
> public class Option<T>  implements Fillable<T>
> {
>     private boolean hasResult;
>     private T result;
>
>     @Override
>     public void addAll(Iterable<? extends T>  source)
>     {
>        for (T t : source)
>        {
>           if (hasResult) throw new IllegalStateException("Not a single result");
>           result = t;
>           hasResult = true;
>        }
>     }
>
>     public T getOrElse(T defaultValue)
>     {
>        return hasResult ? result : defaultValue;
>     }
>
>     public T getOrElse(Factory<? extends T>  defaultFactory)
>     {
>        return hasResult ? result : defaultFactory.make();
>     }
>
>     public<E extends Throwable>  T getOrThrow(Factory<E>  exceptionFactory) throws E
>     {
>        if (hasResult) return result;
>        throw exceptionFactory.make();
>     }
> }
>
> // so the chain above could be written as:
>
>     String s = people.filter(p ->  p.age()>  18)
>                      .map(Person::getName)
>                      .into(new Option<String>()) // hm, new Option<>() does not work here currently :-(
>                      .getOrThrow(() ->  new NoSuchPersonException());
>
>
> Regards, Peter
>
>>
>> Here, the Option has relatively little use on its own; you're going to
>> immediately call one of the various getOrElse methods, and the fact that
>> it is an Option<String>  does not appear in the source code.
>>
>> Similarly, if we added an Option-bearing get-like method to Map (not
>> saying we're going to do this), the same would be true:
>>
>>     String s = map.getAsOption(key)
>>                   .getOrElse(DEFAULT_STR);
>>
>> Since we're not proposing anything that returns List<Optional<T>>  or
>> anything like that, I don't get how your argument applies here, and it
>> almost sounds like you're making the argument of "don't add any new
>> generic container-like types to the library, because you increase the
>> chance that people will use unwieldy generic names", which I can't say
>> makes very much sense to me.  It might be poor taste to have a
>> nineteen-deep nested list of lists, but does that mean we shouldn't give
>> people a generic List class?
>>
>>> I have a guidance rule that 1 level of generics is OK, 2 is
>>> manageable, but 3 means you're probably missing a real domain class.
>>> Optional<T>   just gets you to that 2nd/3rd layer of generics faster
>>> than necessary. I'd also note that those nested generics tend to be
>>> harder for developers to rationalise about.
>>
>> That's a fine rule for your code.  I just don't see how adding
>> Optional<T>  and adding a few methods that return Optional<T>  move people
>> any closer to violating that rule?


More information about the lambda-dev mailing list