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?

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111
fho
  • 6,787
  • 26
  • 71

2 Answers2

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.

ertes
  • 461
  • 4
  • 4
  • Ah, I was about to post something similar, but I will defer to the expertise of someone who actually wrote an AFRP library. :] – C. A. McCann Dec 17 '12 at 20:01
  • It seems that using (A)FRP I wouldn't be restricted to an acyclic graph structure, is this true? – fho Dec 17 '12 at 21:08
  • 3
    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? – Davorak Dec 19 '12 at 22:55
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.

Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135
  • 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. – John L Dec 17 '12 at 23:31
  • @JohnL That is why you have to preach it! Not everybody knows what Oleg did. – Gabriella Gonzalez Dec 18 '12 at 19:10
  • Even people who know what Oleg did often don't understand it. Sadly I'm frequently in this group, at least at first... – John L Dec 20 '12 at 07:36