Skip to content

Conversation

mcschroeder
Copy link

I want to propose including the following function into the streaming prelude:

metamorph :: Monad m => (s -> Maybe (b, s)) -> (s -> a -> s) -> s -> Stream (Of a) m r -> Stream (Of b) m r
metamorph f g = loop
  where
    loop !s stream = case f s of
      Just (b, s') -> Step (b :> loop s' stream)
      Nothing -> case stream of
        Return r -> Return r
        Effect m -> Effect (liftM (loop s) m)
        Step (a :> rest) -> loop (g s a) rest

I have written this function — or more specialized versions of it, like mapMaybeWithState or filterWithState — numerous times, and I think it is suitably useful and general enough to warrant inclusion in the streaming package itself.

The name metamorph is due to Jeremy Gibbons [1], who describes a metamorphism as "an unfold after a fold; typically, it will convert from one data representation to another". Practically speaking, this operation allows one to insert, remove, and transform elements of a stream and to use some internal state while doing so.

I included some usage examples in the Haddock comment of the function, which I reproduce below. (It's tricky finding good examples that aren't overly complex. Personally, I've used this function to de-interleave log-lines before further processing and to transform low-level event streams into more high-level representations, which often involves resolving dynamic references declared previously in the stream and so on.)


Stateful filtering:

>>> let consumerA (n, xs) x = if x > n then (x, (x:xs)) else (n, xs)
>>> let producerA (n, xs) = if null xs then Nothing else Just (head xs, (n, tail xs))
>>> let ascending = metamorph producerA consumerA (0, [])
>>> S.print $ ascending $ S.each [5,4,3,2,1,6,7,5,8,8]
5
6
7
8

A simple line-wrapping algorithm:

>>> let maxLine n xs = last $ filter ((<n) . length . fst) $ zip (map unwords $ inits xs) (tails xs)
>>> let producerW n xs = if null xs then Nothing else Just (maxLine n xs)
>>> let consumerW xs a = xs ++ words a
>>> let wrap n = metamorph (producerW n) consumerW []
>>> S.stdoutLn $ wrap 13 $ S.stdinLn
I have eaten the plums that were in the icebox<Enter>
I have eaten
the plums
that were in
the icebox

[1] Jeremy Gibbons. Metamorphisms: Streaming representation-changers. Science of Computer Programming, 65:108-139, 2007. http://www.comlab.ox.ac.uk/oucl/work/jeremy.gibbons/publications/metamorphisms-scp.pdf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant