19

在我看来,这两个想法之间有很强的联系。我的猜测是,如果有一种方法可以用 Iteratees 表达任意图,那么 FRP 可以用 Iteratees 来实现。但是 afaik 他们只支持链状结构。

有人可以对此有所了解吗?

4

2 回答 2

13

这是相反的方式。AFRP 和流处理之间有很强的联系。事实上 AFRP流处理的一种形式,你可以使用这个成语来实现与管道非常相似的东西:

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

这是 Netwire 中电线类别的扩展。它接收下一个输入块,并在停止生成时返回 Nothing。使用此文件阅读器将具有以下类型:

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

Pipe 是一系列应用函子,因此要将简单的函数应用于流元素,您只需使用fmap

fmap (B.map toUpper) . readFile

为了您的方便,它也是一个profunctors 家族。

最有趣的特点是这是一个替代函子家族。这使您可以路由流并允许多个流处理器在放弃之前“尝试”。这可以扩展到一个成熟的解析库,甚至可以使用一些静态信息进行优化。

于 2012-12-17T19:59:45.437 回答
13

您可以使用流处理器实现有限形式的 FRP。例如,使用该pipes库,您可以定义事件源:

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

...您可能会类似地定义一个图形处理程序,该处理程序采用鼠标坐标并更新画布上的光标:

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

然后,您将使用组合将鼠标事件连接到处理程序:

>>> runProxy $ mouseCoordinates >-> coordHandler

它会按照您期望的方式运行。

就像你说的,这对于单个阶段链很有效,但是更任意的拓扑呢?好吧,事实证明,由于中心Proxy类型pipes是 monad 转换器,您可以通过在它们自身之上嵌套代理 monad 转换器来对任意拓扑进行建模。例如,以下是压缩两个输入流的方法:

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

这表现得像一个柯里化函数。您可以按顺序将其部分应用到每个输入,然后在完全应用时运行它。

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

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

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

它按照您期望的方式运行:

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

这个技巧可以推广到任何拓扑。您可以在“分支、压缩和合并”部分的Control.Proxy.Tutorial中找到更多详细信息。特别是,您应该查看fork它用作示例的组合器,它允许您将流拆分为两个输出。

于 2012-12-17T22:09:24.973 回答