gpt4 book ai didi

Haskell:在不使用 spawn 的情况下拆分管道(广播)

转载 作者:行者123 更新时间:2023-12-03 22:18:09 24 4
gpt4 key购买 nike

这个问题有点 codegolf 和很多新手。

我正在使用很棒的 pipes Haskell 中的库,我想拆分管道以沿多个 channel 发送相同的数据(进行广播)。
Pipes.Concurrent tutorial建议使用 spawn创建邮箱,利用 Output的幺半群状态。
例如,我们可能会做这样的事情:

main = do
(output1, input1) <- spawn Unbounded
(output2, input2) <- spawn Unbounded
let effect1 = fromInput input1 >-> pipe1
let effect2 = fromInput input2 >-> pipe2
let effect3 = P.stdinLn >-> toOutput (output1 <> output2)
...

这种间接通过邮箱真的有必要吗?
我们可以写这样的东西吗?
main = do
let effect3 = P.stdinLn >-> (pipe1 <> pipe2)
...

上面没有编译,因为 Pipe没有 Monoid实例。
这有充分的理由吗?
第一种方法真的是分割管道最干净的方法吗?

最佳答案

有两种方法可以在不使用并发的情况下做到这一点,但都需要注意。

第一种方法是如果 pipe1pipe2很简单Consumer s 永远循环,如:

p1 = for cat f  -- i.e. p1 = forever $ await >>= f
p2 = for cat g -- i.e. p2 = forever $ await >>= g

...那么解决这个问题的简单方法就是写:
for P.stdinLn $ \str -> do
f str
g str

例如,如果 p1只是 print每个值:
p1 = for cat (lift . print)

...和 p2只是将该值写入句柄:
p2 = for cat (lift . hPutStrLn h)

...然后你会像这样组合它们:
for P.stdinLn $ \str -> do
lift $ print str
lift $ hPutStrLn h str

但是,这种简化仅适用于 Consumer s 微不足道的循环。还有另一种更通用的解决方案,即定义 ArrowChoice例如管道。我相信拉基 Pipe s 不允许正确的守法实例,而是基于推送的 Pipe做:
newtype Edge m r a b = Edge { unEdge :: a -> Pipe a b m r }

instance (Monad m) => Category (Edge m r) where
id = Edge push
(Edge p2) . (Edge p1) = Edge (p1 >~> p2)

instance (Monad m) => Arrow (Edge m r) where
arr f = Edge (push />/ respond . f)
first (Edge p) = Edge $ \(b, d) ->
evalStateP d $ (up \>\ unsafeHoist lift . p />/ dn) b
where
up () = do
(b, d) <- request ()
lift $ put d
return b
dn c = do
d <- lift get
respond (c, d)

instance (Monad m) => ArrowChoice (Edge m r) where
left (Edge k) = Edge (bef >=> (up \>\ (k />/ dn)))
where
bef x = case x of
Left b -> return b
Right d -> do
_ <- respond (Right d)
x2 <- request ()
bef x2
up () = do
x <- request ()
bef x
dn c = respond (Left c)

这需要一个新类型,以便类型参数按照 ArrowChoice 的顺序排列。预计。

如果您不熟悉基于推送的术语 Pipe ,它基本上是一个 Pipe从最上游的管道而不是最下游的管道开始,它们都具有以下形状:
a -> Pipe a b m r

将其视为 Pipe在从上游接收到至少一个值之前,它不能“运行”。

这些基于推送的 Pipe s 是传统拉动的“双重” Pipe s,用自己的组合运算符和身份完成:
(>~>) :: (Monad m)
=> (a -> Pipe a b m r)
-> (b -> Pipe b c m r)
-> (a -> Pipe a c m r)

push :: (Monad m)
-> a -> Pipe a a m r

...但单向 Pipes默认情况下,API 不会导出它。您只能从 Pipes.Core 获取这些运算符(并且您可能希望更仔细地研究该模块以建立对它们如何工作的直觉)。该模块显示基于推送的 Pipe s 和基于拉的 Pipe s 都是更通用的双向版本的特殊情况,理解双向情况是您了解它们为什么彼此对偶的方式。

一旦你有 Arrow例如,对于基于推送的管道,您可以编写如下内容:
p >>> bifurcate >>> (p1 +++ p2)
where
bifurcate = Edge $ pull ~> \a -> do
yield (Left a) -- First give `p1` the value
yield (Right a) -- Then give `p2` the value

然后你会使用 runEdge完成后将其转换为基于拉动的管道。

这种方法有一个主要缺点,即您不能自动将基于拉的管道升级为基于推送的管道(但通常不难弄清楚如何手动进行)。例如,升级 Pipes.Prelude.map做一个基于推送的 Pipe ,你会写:
mapPush :: (Monad m) => (a -> b) -> (a -> Pipe a b m r)
mapPush f a = do
yield (f a)
Pipes.Prelude.map f

然后它有正确的类型被包裹在 Arrow 中。 :
mapEdge :: (Monad m) => (a -> b) -> Edge m r a b
mapEdge f = Edge (mapPush f)

当然,更简单的方法是从头开始编写:
mapEdge f = Edge $ push ~> yield . f

使用最适合您的方法。

事实上,我想出了 ArrowArrowChoice实例正是因为我试图回答与您完全相同的问题:您如何在不使用并发的情况下解决这些问题?我在另一个 Stack Overflow 答案 here 中写了一个关于这个更笼统主题的长答案。 ,我在这里描述了如何使用这些 ArrowArrowChoice实例将并发系统提炼成等效的纯系统。

关于Haskell:在不使用 spawn 的情况下拆分管道(广播),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19758744/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com