What is the connection between Iteratees and FRP?
Asked Answered
H

2

19

It seems to me that there is a strong connection between the two ideas. My guess is that FRP could be implemented in terms of Iteratees if there would be a way to express arbitrary graphs with Iteratees. But afaik they only support chain-like structures.

Could someone shed some light on this?

Housecarl answered 17/12, 2012 at 19:20 Comment(0)
R
13

It's the other way around. There is a strong connection between AFRP and stream processing. In fact AFRP is a form of stream processing, and you can use the idiom to implement something very similar to pipes:

data Pipe m a b =
    Pipe {
      cleanup :: m (),
      feed    :: [a] -> m (Maybe [b], Pipe m a b)
    }

That's an extension of wire categories as found in Netwire. It receives the next chunk of input and returns Nothing when it stops producing. Using this a file reader would have the following type:

readFile :: (MonadIO m) => FilePath -> Pipe m a ByteString

Pipe is a family of applicative functors, so to apply a simple function to the stream elements you could just use fmap:

fmap (B.map toUpper) . readFile

For your convenience it's also a family of profunctors.

The most interesting feature is that this is a family of Alternative functors. That allows you to route streams around and allow multiple stream processors to "try" before giving up. This can be extended to a full-fledged parsing library that can even use some static information for optimization purposes.

Rexer answered 17/12, 2012 at 19:59 Comment(3)
Ah, I was about to post something similar, but I will defer to the expertise of someone who actually wrote an AFRP library. :]Dun
It seems that using (A)FRP I wouldn't be restricted to an acyclic graph structure, is this true?Housecarl
It is not clear to me from your answer what if any aspects of AFRP can not be implemented in terms or the recent stream possessing libraries conduit/pipes?Terms
H
13

You can implement a limited form of FRP using stream processors. For example, using the pipes library, you might define a source of events:

mouseCoordinates :: (Proxy p) => () -> Producer p MouseCoord IO r

... and you might similarly define a graphical handler that takes mouse coordinates and updates a cursor on a canvas:

coordHandler :: (Proxy p) => () -> Consumer p MouseCoord IO r

Then you would hook up the mouse events to the handler using composition:

>>> runProxy $ mouseCoordinates >-> coordHandler

And it would run just the way you expect.

Like you said, this works well for a single chain of stages, but what about more arbitrary topologies? Well, it turns out that since the central Proxy type of pipes is a monad transformer, you can model any arbitrary topology just by nesting proxy monad transformers on top of themselves. For example, here is how you would zip two input streams:

zipD
 :: (Monad m, Proxy p1, Proxy p2, Proxy p3)
 => () -> Consumer p1 a (Consumer p2 b (Producer p3 (a, b) m)) r
zipD () = runIdentityP $ hoist (runIdentityP . hoist runIdentityP) $ forever $ do
    a <- request ()               -- Request from the outer Consumer
    b <- lift $ request ()        -- Request from the inner consumer
    lift $ lift $ respond (a, b)  -- Respond to the Producer

This behaves like a curried function. You partially apply it to each input sequentially and you can then run it when it is fully applied.

-- 1st application
p1 = runProxyK $ zipD   <-< fromListS [1..]

-- 2nd application
p2 = runProxyK $ p2     <-< fromListS [4..6]

-- 3rd application
p3 = runProxy  $ printD <-< p3

It runs just the way you expect:

>>> p3
(1, 4)
(2, 5)
(3, 6)

This trick generalizes to any topology. You can find a lot more details about this in Control.Proxy.Tutorial in the "Branches, zips, and merges" section. In particular, you should check out the fork combinator it uses as an example, which lets you split a stream into two outputs.

Hartwell answered 17/12, 2012 at 22:9 Comment(3)
I'm pretty sure this is true of all sane iteratee libraries. Oleg used it some time ago. I'm not sure why nobody ever seems to remember that it's possible; the technique is very useful.Give
@JohnL That is why you have to preach it! Not everybody knows what Oleg did.Hartwell
Even people who know what Oleg did often don't understand it. Sadly I'm frequently in this group, at least at first...Give

© 2022 - 2024 — McMap. All rights reserved.