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

2023-12-09

这个问题有点代码高尔夫,而且很新鲜。

我正在使用很棒的pipesHaskell 中的库,我想拆分一个管道以沿多个通道发送相同的数据(进行广播)。 这Pipes.Concurrent教程建议使用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实例。 这有充分的理由吗? 第一种方法真的是最干净的管道分割方法吗?


有两种不使用并发的方法可以做到这一点,但都有注意事项。

第一种方法是如果pipe1 and pipe2只是简单Consumer永远循环如下:

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)

... and p2只是将该值写入句柄:

p2 = for cat (lift . hPutStrLn h)

...然后你可以像这样组合它们:

for P.stdinLn $ \str -> do
    lift $ print str
    lift $ hPutStrLn h str

然而,这种简化仅适用于Consumer这是一个简单的循环。还有另一种更通用的解决方案,即定义一个ArrowChoice管道的实例。我相信基于拉动Pipes 不允许正确的守法实例,而是基于推送Pipes do:

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在从上游接收到至少一个值之前,它无法“运行”。

这些基于推送的Pipes 是传统拉式的“双重”Pipes,具有自己的组合运算符和身份:

(>~>) :: (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(您可能想更仔细地研究该模块,以建立对它们如何工作的直觉)。该模块显示两者都基于推送Pipes 和基于拉动的Pipes 都是更通用的双向版本的特殊情况,了解双向情况就是您了解为什么它们彼此对偶的方式。

一旦你有一个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

使用最适合您的方法。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell:在不使用spawn的情况下分割管道(广播) 的相关文章

  • Haskell:确定函数数量的函数?

    可以写一个函数吗arity a gt Integer确定任意函数的数量 使得 gt arity map 2 gt arity foldr 3 gt arity id 1 gt arity hello 0 是的 这可以非常非常容易地完成 ar
  • Haskell 五个独特的 Wordle 单词

    为了好玩 我正在尝试解决 Matt Parker 在他的 Haskell 频道 Standup Maths in Haskell 频道的链接视频中谈到的与 Wordle 相关的问题 基本上 找到 5 个没有任何共同字母的 5 个字母单词 因
  • 浏览前奏的源代码会带来奇怪的情况

    我一直在寻找的定义seq并遇到了这个奇怪的事情 为什么所有这些函数都有相同 相似的定义 seq a gt b gt b seq let x x in x inline a gt a inline let x x in x lazy a gt
  • parList 和 parBuffer 如何选择?

    我从 haskell 并行开始 我已经成功学习了如何使用一些策略 例如 r0 rseq rdeepseq parList parMap 现在我正在进一步寻求更高的效率 所以这是我的问题 有什么区别parList and parBuffer
  • 如何获得具有超载字段名称的经典镜头?

    我正在尝试为具有相同字段名称的记录构建镜头 除此之外 我试图 包装 扩展 这些基本记录 并希望相同的字段名称适用于包装 扩展的记录 我相信 优雅的镜头就是这样做的 我如何让以下内容发挥作用 Data types for context of
  • 如何在Haskell中定义一个允许统一访问不同记录的类?

    我有两条记录 它们都有一个我想要提取以显示的字段 我如何安排事物以便可以使用相同的功能来操纵它们 由于它们有不同的字段 在本例中firstName and buildingName 这是它们的名称字段 它们每个都需要一些 适配器 代码来映射
  • 对 F# 中任意嵌套级别的列表求和

    我正在尝试创建一个 F 函数 它将返回列表的总和int任意嵌套 IE 它将适用于list
  • Pandoc“无法找到已安装模块的模块...”

    我目前正在尝试使用 pandoc 作为 Haskell 模块 而不是程序 将 MediaWiki 文本转换为其他格式 我们假设这个程序 import Text Pandoc Readers MediaWiki main do print f
  • 运行程序的最佳 Haskell 库是什么? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 如果我要将一个程序投入生产 我需要该程序做几件事才能将其视为 可操作 也就是说 工程师和操作人员以可测量
  • 模式匹配需要圆括号来表示非空列表而不是方括号

    在 Haskell 中 为什么模式匹配期望列表在不为空时有圆括号而不是方括号 当它尝试与空列表 方括号 进行模式匹配时 为什么它不遵循相同的约定 圆括号不应该专门为元组保留吗 例如 下面不起作用 third Integral a gt a
  • 模式匹配中的 Monoid mempty

    我尝试写一个通用的maximum功能类似于Prelude 我的第一个天真的方法如下所示 maximum F Foldable a Ord b gt a b gt Maybe b maximum mempty Nothing maximum
  • 哈斯克尔状态单子

    是否putState Monad 的函数会更新实际状态还是仅返回具有新值的新状态 我的问题是 State Monad 可以在命令式设置中像 全局变量 一样使用吗 并且确实put修改 全局变量 我的理解是 不 它不会修改初始状态 但是使用单子
  • 仪器化状态单子

    我正在努力给予Monad and MonadState的实例State 计算的数量 gt gt return get and put运营 data Counts Counts binds Int returns Int gets Int p
  • 绑定变量时 Haskell 中的无限循环

    下面的 Haskell 代码不会终止 有人可以解释一下为什么吗 谢谢 f let x 10 in let x x x in x 我认为解释器首先绑定 x 10 然后将 x x 计算为 100 并绑定 x 100 环境变为 x 100 那么整
  • yesod——密码保护临时站点

    我正在尝试设置 yesod 网络服务器的临时实例 我想知道是否有一些简单的方法可以使整个站点受到密码保护 具体来说 我希望能够提示那些导航到我的网站的人提供凭据 经过身份验证后 它应该像典型站点一样运行 但如果他们无法验证自己的身份 他们就
  • 应用交换律

    带有效果的应用程序编程 http staff city ac uk ross papers Applicative html麦克布莱德和帕特森的论文提出了互换法 u lt gt pure x pure f gt f x lt gt u 为了
  • 使用 Haskell 的欧拉项目 #1

    import Data Set euler Int euler sum x x lt nums where nums Data Set toList Data Set union Data Set fromList 3 6 999 Data
  • 表达式“ap zip tail”如何工作

    我想知道怎么写f x zip x tail x 点免费 所以我使用了pointfree程序 结果是f ap zip tail ap作为 Control Monad 的函数 我不明白点自由定义是如何工作的 如果我能从类型的角度去理解的话 希望
  • 不同 hs 文件中的函数分离时堆栈空间溢出

    我有一个巨大的 haskell 文件 它编译和运行没有任何问题 我想将一些函数和类型定义放在通用 hs 文件中的单独模块中 然后将其导入我的主模块中 虽然主程序编译时没有任何错误 它还编译导入的模块 但当我尝试运行它时 出现堆栈空间溢出 I
  • 计算两点之间的距离(Haskell)

    给定两个元组的输入 我希望能够使用以下公式计算两点之间的距离 距离 sqrt x1 x2 2 y1 y2 2 所以我希望函数调用和输出如下所示 gt distance 5 10 3 5 5 385 当我尝试运行下面的代码时 它告诉我输入 w

随机推荐