explode()

Remi Forax forax at univ-mlv.fr
Tue Jan 29 13:57:35 PST 2013


On 01/29/2013 07:19 PM, Brian Goetz wrote:
> The other alternative is more overloading:
>
>   explode(Exploder<T,U>) // current
>   explodeToCollection(Function<T,Collection<U>>)
>   explodeToStream(Function<T,Stream<U>>)
>   explodeToArray(Function<T,U[]>)
>
> Note that many people think "all they want" is the second one, but 
> they're wrong.
>
> IF we can only have one, there's no discussion -- it's the first one, 
> since one can relatively easily derive the others from the first. 
> However, the first does tend to induce head-explosion.

like Collector, you can have one explode(Exploder<T,U>) and several way 
to create Exploders
like explode(Exploder.toCollection(User::getPermissions))

Rémi

>
>
>
> On 1/29/2013 1:14 PM, Brian Goetz wrote:
>>> Hmm. So the methods I proposed, Consumer.accept(Collection) and
>>> Consumer.accept(Stream), are functionally identical to
>>> Collection.forEach(consumer) and Stream.forEach(consumer), and thus 
>>> have
>>> little benefit except in that various Consumer implementations might be
>>> able to override them and do something more efficient. Which in turn
>>> means that these forEach() methods would have to delegate straight to
>>> them; they become pure conveniences only.
>>
>> Mostly so, though I could imagine (I realize I'm undermining my earlier
>> position) that Sink.acceptAll(Stream) might be able to do something
>> better in the case when we explode an element to an infinite stream.
>> Currently, exploding an element to an infinite stream:
>>
>>    ints.explode(i -> Streams.repeat(() -> i)).getFirst()
>>
>> would never terminate, even though it theoretically could.
>>
>> Having either the acceptAll() method on Consumer OR the send(Stream)
>> method on DownstreamThingie, would leave us a door to eventually fix
>> that issue; asking users to do stream.forEach(consumer) would make it
>> harder to do so.
>>
>> My worry is that, especially given the late date, I'd like to minimize
>> the number of default methods on SAM types.
>>
>>> But when considering those use cases, one of them may arise if drop
>>> Downstream and have explode() use Consumer instead. In that case, does
>>> it break explode() if we have users just using stream.forEach(consumer)
>>> and collection.forEach(consumer) instead of downstream.send(stream) and
>>> downstream.send(collection)?
>>
>> Another possibility is to return to an intermediate position, where
>> there was a SAM for "function from singleton to multi", such as the
>> Exploder you suggest.  Once we do that, then putting a renamed
>> Downstream in Exploder makes it clearer:
>>
>> interface Exploder<T,U> {
>>      void explode(T input, ShrapnelCatcher<U> catcher);
>>
>>      interface ShrapnelCatcher<U> {
>>          void accept(U u);
>>          default void acceptAll(Collection<U> c) { ... }
>>          ...
>>      }
>> }
>>
>>
>>
>>>
>>>
>>> On Thu, Jan 24, 2013 at 2:43 PM, Kevin Bourrillion <kevinb at google.com
>>> <mailto:kevinb at google.com>> wrote:
>>>
>>>     explode() is aptly named for what it did to my brain when I
>>>     encountered it. :-)
>>>
>>>     A few things are adding up to make it impenetrable.
>>>
>>>     1. The vast majority of the Stream API, save the obvious exceptions
>>>     like forEach(), is solidly in the functional style. Collectors
>>>     aren't really, but as long as I use the prepackaged ones, it
>>>     /feels/ fairly functional anyway. Now suddenly there's explode,
>>>     which doesn't feel functional at all. Instead, I need to think of
>>>     the bit of code I provide to it as an active participant in a
>>>     pipeline. Things are fed to me, I feed things on down the line. 
>>> This
>>>     makes it different, and different is automatically confusing. This
>>>     can't really be /corrected/, but seems worth nothing; maybe we can
>>>     account for the difference somehow.
>>>
>>>     2. In attempting to learn it, I'm confronted with a type,
>>>     Stream.Downstream<U>, which I won't have heard of before that
>>>     moment, and whose name reveals little.  Is there any particular
>>>     reason that Sink/Consumer, with its accept(T) method, should
>>>     not also have "default" methods implementing accept(Collection<T>)
>>>     and accept(Stream<T>) and accept(T[])?  I can't think of any; those
>>>     sound just plain handy.  And if we added those, would there be
>>>     enough value in preserving Downstream as an identically-signatured
>>>     type?  We could dispense with it, as I've done below.
>>>
>>>     3. Except sometimes there /is/ value in having a special type even
>>>     if its signature is identical to another.  I suspect that a custom
>>>     type could help quite a bit in this case:
>>>
>>>         interface Exploder<T,R> {
>>>            /**
>>>             * Maps {code input} to zero or more instances of R, sending
>>>         these
>>>             * to {@code resultConsumer} to be included in the 
>>> results of
>>>             * {@link Stream#explode(Exploder)}.
>>>             */
>>>            void explode(T input, Consumer<R> resultConsumer);
>>>         }
>>>
>>>
>>>     4. Then there is the name "explode". It's... okay. "unpack"? 
>>> Nah. It
>>>     seems maybe 40% possible that a great name is out there eluding
>>> us....
>>>
>>>
>>>
>>>     --
>>>     Kevin Bourrillion | Java Librarian | Google, Inc. 
>>> |kevinb at google.com
>>>     <mailto:kevinb at google.com>
>>>
>>>
>>>
>>>
>>> -- 
>>> Kevin Bourrillion | Java Librarian | Google, Inc. |kevinb at google.com
>>> <mailto:kevinb at google.com>



More information about the lambda-libs-spec-observers mailing list