由于某些高度固定的开销(如数据包标头或建立连接),您需要执行一系列操作,这些操作更倾向于以块的形式执行。限制是有时下一个操作取决于前一个操作的结果,在这种情况下,所有挂起的操作都会立即执行。
示例:
mySession :: Session IO ()
a <- readit -- nothing happens yet
b <- readit -- nothing happens yet
c <- readit -- nothing happens yet
if a -- all three readits execute because we need a
then write "a"
else write "..."
if b || c -- b and c already available
...
这让我想起了如此多的Haskell概念,但我不能指责它。
当然,你可以做一些明显的事情:
[a,b,c] <- batch([readit, readit, readit])
但是我想隐藏用户的分块事实以获得光滑的目的。
不确定Session是否是正确的词。也许你可以建议一个更好的? (数据包,批处理,块和延迟会浮现在脑海中。)
我觉得我昨晚在手机上看到了一个非常好的答案,但是当我今天回来寻找它时,它已经消失了。我在做梦吗?
答案 0 :(得分:3)
我认为你不能完全按照自己的意愿行事,因为你所描述的利用haskell的懒惰评估来评估a
强制计算b
和c
的行动,并且没有办法seq
未指定的值。
我能做的就是破解一个monad变换器,它延迟了通过>>
排序的动作,以便它们可以一起执行:
data Session m a = Session { pending :: [ m () ], final :: m a }
runSession :: Monad m => Session m a -> m a
runSession (Session ms ma) = foldr (flip (>>)) (return ()) ms >> ma
instance Monad m => Monad (Session m) where
return = Session [] . return
s >>= f = Session [] $ runSession s >>= (runSession . f)
(Session ms ma) >> (Session ms' ma') =
Session (ms' ++ (ma >> return ()) : ms) ma'
这违反了某些monad laws,,但允许您执行以下操作:
liftIO :: IO a -> Session IO a
liftIO = Session []
exampleSession :: Session IO Int
exampleSession = do
liftIO $ putStrLn "one"
liftIO $ putStrLn "two"
liftIO $ putStrLn "three"
liftIO $ putStrLn "four"
trace "five" $ return 5
并获取
ghci> runSession exampleSession
five
one
two
three
four
5
ghci> length (pending exampleSession)
4
答案 1 :(得分:1)
答案 2 :(得分:0)
您可以使用unsafeInterleaveIO
功能。这是一个危险的功能,如果不仔细使用,可能会给您的程序带来错误,但它会满足您的要求。
您可以将它插入到示例代码中,如下所示:
lazyReadits :: IO [a]
lazyReadits = unsafeInterleaveIO $ do
a <- readit
r <- lazyReadits
return (a:r)
unsafeInterleaveIO
使整个行动变得懒惰,但一旦开始评估它就会评估它是否严格。这意味着在上面的示例中:readit
将在测试返回的列表是否为空时运行。如果我改为使用mapM unsafeInterleaveIO (replicate 3 readit)
,那么readit
只会在评估列表的实际元素时运行,这会使列表的内容依赖于其元素的顺序检查,这是unsafeInterleaveIO
如何引入错误的一个例子。