5 ms·
In Haskell terms, Collectors have an Applicative instance, and teeing corresponds to the liftA2 function: > liftA2 :: (x -> y -> z) -> Fold a x -> Fold a y ->
by danidiaz 5y ago
In Haskell terms, Collectors have an Applicative instance, and teeing corresponds to the liftA2 function:
> liftA2 :: (x -> y -> z) -> Fold a x -> Fold a y -> Fold a z
The "teeing" functionality is actually one of the "selling points" of the Collector-like library, not a hidden gem:
> This module provides efficient and streaming left folds that you can combine using Applicative style.
It's curious how the same abstraction can have different emphases across languages.
Collectors are also Comonads:
- You can always extract a value of the type that parameterizes the Collector, just by "closing" it.
> extract :: Fold a x -> x
- You could, in theory, "duplicate" a Collector<X> and get a Collector<Collector<X>>. This seems like a dumb function, but it would allow you for example to feed different Streams to the "same" collector, by duplicating it before consuming a Stream, the taking the result Collector, duplicating it again, passing it to another Stream...
> duplicate :: Fold a x -> Fold a (Fold a x)
http://hackage.haskell.org/package/foldl-1.4.11/docs/Control-Foldl.html http://hackage.haskell.org/package/foldl-1.4.11/docs/Control...
- cole-k 5y agoI imagine part of the reason is because Java has the idiomatic usage of list.stream().functionalOperations.collect(Collectors.toList()) whereas in Haskell you can just do whatever on your lists and it should fuse... or so I thought. Clearly I don't know too much about which way is the "right way" in Haskell since I haven't used Control.Foldl before and I just sort of assumed fusion would happen at least for most list operations.
- lmm 5y agoYou should look into iteratees. It's the same insight: if you have the concept of something that receives values and eventually yields a value of a given type, that's a structure that has some very nice algebraic properties (e.g. they're monads).
- danidiaz 5y agoI'm not sure you could implement a useful flatMap() for Collector-like types, at least without forcing the collector to hold all the received values in memory, which would defeat the purpose. It would be like a function that takes - a Collector that produces an X - a function that takes an X and returns a Collector that produces an Y and returns a Collector that produces an Y. The thing is: while being fed, the result Collector should first feed the initial Collector and, at some time, "switch" to the Collector produced by the function. But when to perform the switch?
- lmm 5y agoIteratees use a slightly different interface: when you feed one values you get the next iteratee state (which is either "done" or "in-progress", roughly - you can feed an EOF if you want an iteratee to finish, you don't have a "current" value until then) and any unconsumed values (possibly all of them). It's counterintuitive to start with, but it makes for a really nice representation.
- danidiaz 5y agoHere's a possible implementation of "duplicate": https://stackoverflow.com/a/67475265/1364288 https://stackoverflow.com/a/67475265/1364288