Composing or piping multiple Folds

Control.Foldl packs a lot of interesting folds and folds transformers such as premap and purely but nowhere was I able to find a ‘folds transformer’, i.e. a function of type

transfold :: Fold a b -> Fold b c -> Fold a c and that would allow me to extract and compose the step functions of a plurality of folds, making the following code type check:

fold (transfold increment1 double) [1,2,3] // purely hypothetically: expecting: [3,5,7] assuming a 'double' and an 'increment1' fold.

Because there is no such utility one is stuck writing ‘parallel’ folds with applicatives as in

fold ( (/) <$> length <*> sum ) [1,2,3] // assuming a folder 'length'

where the folds don’t share results, or composing not folds per se but plain functions injected into a fold’s step function as in:

fold (premap (*2) increment1) [1,2,3] // [3,5,7] assuming an 'increment1' fold

which is fine but wastes the potential of preexisting folds which by the way could compose with each other.

In a nutshell, then, it seems that the whole library is premised on the idea that folds in the scope of a single call of a fold function can never pipe their results. I am not sure if this premise is fine if you want to make the most of already usable folds.

Is there a way around this shortcoming?

You should remember that a Fold a b takes a “stream” of as and produces a single value of type b, so the transfold function should have type Fold a [b] -> Fold b c -> Fold a c. I think these kinds of functions are usually called stream transformers. They are present in streaming libraries like pipes, conduit, streaming and streamly (streamly also uses the foldl library).

Streamly has the following example in its README which shows how you can pipe a stream through multiple transformers. Composition is simply done with function application (x & f & g = g (f x)).

import Streamly
import qualified Streamly.Prelude as S
import Data.Function ((&))

main = S.drain $
       S.repeatM getLine
     & fmap read
     & S.filter even
     & S.takeWhile (<= 9)
     & fmap (\x -> x * x)
     & S.mapM print
1 Like

Hello, thanks for your reply, you might be correct about the use case of Folds – and the concept of their eagerly consuming streams of data. But to turn my initial question into a remark, I would say it is surprising that if you want to express a filter as a Fold, you cannot combine it with any other Fold inside a call to the fold function as per this library (Control.Fold). You have to either prefilter and filter outside of the call. This strikes me a very akward, given that filters are paradigmatic Folds and that all Folds have step functions matching in kind: x -> a -> x.