Log*_*ins 13 select haskell channel stm
Go语言有一个select语句,可用于轮询多个通道并执行特定操作,具体取决于哪个通道首先是非空的.
例如
select {
case a := <- chanA:
foo(a)
case b := <- chanB:
baz(b)
case c := <- chanC:
bar(c)
}
Run Code Online (Sandbox Code Playgroud)
这将等到chanA,chanB或者chanC非空,然后如果例如chanB非空,它将读取chanB并存储结果b,然后调用baz(b).default:还可以添加一个子句,这意味着select语句不会在通道上等待,而是default在所有通道都为空的情况下执行该子句.
TChan在Haskell中为STM实现这样的事情的最佳方法是什么?它可以通过if-else链天真地完成:检查每个chan isEmptyChan,如果它不是空的,那么从它读取并调用适当的函数,或者retry如果所有通道都是空的则调用.我想知道是否会有更优雅/惯用的方式来做到这一点?
请注意,Go的select语句在其情况下也可以包含send语句,并且只有在其通道为空时才会完成send语句.如果功能也可以重复,那将是很好的,虽然我不确定是否会有一种优雅的方式.
只是略微相关,但我刚注意到的东西,我不知道在哪里发布它:在描述中的Control.Monad.STM页面上有一个拼写错误retry:
"该实现可能会阻止该线程,直到它读取的其中一个TVAR已被更新."
Yur*_*ras 11
您可以select使用orElse实现语义(包括读取和写入)(注意:它特定于ghc.)例如:
forever $ atomically $
writeTChan chan1 "hello" `orElse` writeTChan chan2 "world" `orElse` ...
Run Code Online (Sandbox Code Playgroud)
这个想法是当一个动作重试时(例如你正在写chan但是它已经满了;或者你正在从chan读取,但它是空的),第二个动作就会被执行.该default声明只是return ()链中的最后一个动作.
补充:正如@Dustin所说,go选择随机分支是有充分理由的.可能最简单的解决方案是在每次迭代时改组操作,在大多数情况下应该没问题.正确复制go语义(仅对shuffle active branches)进行复制有点困难.可能需要手动检查isEmptyChan所有分支机构.
避免饥饿
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
]
Run Code Online (Sandbox Code Playgroud)
为了永远避免,你可以改为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
Run Code Online (Sandbox Code Playgroud)
扩展该技术,你可以选择报告它是否执行了一个动作,或者它执行了哪个动作,甚至循环,直到所有动作都阻止等为止.