在我看来,这两种观点之间存在着密切的联系。我的猜测是,如果有一种方法可以用迭代器表示任意图形,那么FRP可以用Iteratees来实现。但是afaik他们只支持链式结构。
有人可以对此有所了解吗?
答案 0 :(得分: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
为方便起见,它也是一个系列的人。
最有趣的特点是,这是一系列替代函子。这允许您路由流并允许多个流处理器在放弃之前“尝试”。这可以扩展到一个完整的解析库,它甚至可以使用一些静态信息进行优化。
答案 1 :(得分: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
这就像一个curried函数。您可以按顺序将其部分应用于每个输入,然后可以在完全应用它时运行它。
-- 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
组合器作为示例,它允许您将流拆分为两个输出。