如何为Haskell STM通道实现Go的select语句?

时间:2014-07-07 13:31:44

标签: haskell select channel stm

Go语言有一个select语句,可用于轮询多个通道并执行特定操作,具体取决于哪个通道首先是非空的。

E.g。

select {
  case a := <- chanA:
    foo(a)
  case b := <- chanB:
    baz(b)
  case c := <- chanC:
    bar(c)
}

这将等到chanAchanBchanC非空,然后如果例如chanB非空,则会从{{ 1}}并将结果存储在chanB中,然后调用b。还可以添加baz(b)子句,这意味着default:语句不会在通道上等待,如果所有通道都为空,它将执行select子句。

在Haskell中为STM default实现这样的事情的最佳方法是什么?它可以通过if-else链天真地完成:检查每个chan TChan,如果它不是空的,那么从中读取并调用适当的函数,或者如果所有通道都调用isEmptyChan是空的。我想知道是否会有更优雅/惯用的方法来做到这一点?

请注意,Go的retry语句在其情况下也可以包含send语句,并且只有在其通道为空时才会完成发送语句。如果这个功能也可以重复,那就好了,虽然我不确定是否会有一种优雅的方式。

只是略微相关,但我刚注意到的一些内容,我不确定在哪里发帖:select的说明中的Control.Monad.STM页面上有拼写错误:

“实施可能会阻止该主题,直到其中一个已读取的视频已更新。”

2 个答案:

答案 0 :(得分:11)

您可以使用orElse实现select语义(包括读取和写入)(注意:它特定于ghc。) 例如:

forever $ atomically $
  writeTChan chan1 "hello" `orElse` writeTChan chan2 "world" `orElse` ...

这个想法是当一个动作重试时(例如你正在写chan但是它已经满了;或者你正在从chan读取,但它是空的),第二个动作就会被执行。 default语句只是return ()作为链中的最后一个操作。

添加: 正如@Dustin所说,go选择随机分支是有充分理由的。可能最简单的解决方案是在每次迭代时改组操作,在大多数情况下应该没问题。正确复制go语义(仅对shuffle active branches进行复制)有点困难。可能需要手动检查所有分支的isEmptyChan是否可行。

答案 1 :(得分:5)

避免饥饿

foreverK :: (a -> m a) -> a -> m ()
foreverK loop = go
 where go = loop >=> go

-- Existential, not really required, but feels more like the Go version
data ChanAct = Action (TChan a) (a -> STM ())

perform :: STM ()
perform (Action c a) = readTChan c >>= a

foreverSelectE :: [ChanAct] -> STM ()
foreverSelectE = foreverSelect . map perform

foreverSelect :: [STM ()] -> STM ()
foreverSelect = foreverK $ \xs -> first xs >> return (rotate1 xs)

-- Should only be defined for non-empty sequences, but return () is an okay default.
-- Will NOT block the thread, but might do nothing.
first :: [STM ()] -> STM ()
first = foldr orElse (return ())

-- Should only be defined for non-empty sequences, really.
-- Also, using a list with O(1) viewL and snoc could be better.
rotate1 :: [a] -> [a]
rotate1 []    = []
rotate1 (h:t) = t ++ [h]

example = foreverSelectE
    [ Action chanA foo
    , Action charB baz
    , Action chanC bar
    ]

为了永远避免,你可以改为拥有一个mkSelect :: [STM ()] -> STM (STM ())来“隐藏”一个TVar [STM()]并在每次使用它时将其旋转,如:

example1 :: STM ()
example1 = do
    select <- mkSelect [actions] -- Just set-up
    stuff1
    select -- does one of the actions
    stuff2
    select -- does one of the actions

main = OpenGL.idleCallback $= atomically example1

扩展该技术你可以有一个选择报告它是否执行了一个动作或者它执行了哪个动作,甚至循环直到所有动作都阻止等等。