我有一个函数f :: [a] -> b
,可以在无限列表上运行(例如take 5
,takeWhile (< 100) . scanl (+) 0
等等。我想用严格的monadic动作(例如randomIO
)生成的值来提供这个函数。
从this question开始,我了解到repeat
和sequence
技巧方法不适用于严格的monad,如下例所示:
import Control.Monad.Identity
take 5 <$> sequence (repeat $ return 1) :: Identity [Int]
-- returns `Identity [1,1,1,1,1]`
-- works because Identity is non-strict
take 5 <$> sequence (repeat $ return 1) :: IO [Int]
-- returns `*** Exception: stack overflow`
-- does not work because IO is strict
所以,相反,我考虑使用函数&#34; inside&#34; monadic背景。我受到这个circular programming example的启发并尝试过:
let loop = do
x <- return 1
(_, xs) <- loop
return (take 5 xs, x:xs)
in fst loop :: Identity [Int]
-- Overflows the stack
和
import Control.Monad.Fix
fst <$> mfix (\(_, xs) -> do
x <- return 1
return (take 5 xs, x:xs)) :: Identity [Int]
-- Overflows the stack
甚至
{-# LANGUAGE RecursiveDo #-}
import System.Random
loop' = mdo
(xs', xs) <- loop xs
return xs'
where loop xs = do
x <- randomIO
return (take 5 xs, x:xs)
print $ loop'
-- Returns a list of 5 identical values
但这些都不起作用。我还尝试了一种Conduit
方法,即使在Identity
情况下也不起作用:
import Conduit
runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5
因此我想知道:
为什么没有&#34;循环&#34;接近上述工作?
如果存在不涉及unsafeInterleaveIO
的解决方案。 (也许iteratee
s,Arrow
s?)
答案 0 :(得分:4)
我在这里使用randomIO只是为了简化示例。在实践中,我想处理通过套接字接收的消息
没有unsafeInterleaveIO
,是不可能的。一天结束时的问题是必须对IO
个动作进行排序。虽然对引用透明值的评估顺序无关紧要,但IO
行为的评估顺序可能无关紧要。如果你想要通过套接字接收的所有消息的惰性无限列表,你必须先通知Haskell在IO
动作的顺序中哪个适合(除非你使用unsafeInterleaveIO
)。
您正在寻找的Arrow
抽象被称为ArrowLoop
,但它对于严格的monad 正确的紧缩法也有问题。
乍一看,它可能看起来像MonadFix
(通过mdo
或mfix
表现出来)也是一种解决方案,但挖掘得更深shows that fixIO
has problems,就像{来自loop
的{1}}。
但是,有时ArrowLoop
操作必须一个接一个地运行的限制有点过分,这就是IO
的用途。引用docs
unsafeInterleaveIO
允许unsafeInterleaveIO
计算延迟延迟。传递IO
类型的值时,只有在需要IO a
的值时才会执行IO
。
现在,即使您明确表示您没有想要一个a
解决方案,因为我希望能说服您这是一种方法,这里是:
unsafeInterleaveIO
这是适用于随机数字:
interweavingRepeatM :: IO a -> IO [a]
interweavingRepeatM action = unsafeInterleaveIO ((:) <$> action <*> interweavingRepeatM action)
答案 1 :(得分:3)
Alec's answer涵盖了您的一般问题。以下是 conduit 和类似的流媒体库。
我还尝试了一种
Conduit
方法,即使在Identity
案例中也无效:import Conduit runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5
虽然流媒体库通常用于避免您提到的那种困难(参见Pipes.Tutorial
的开头评论),但他们假设您将使用其流类型而不是列表。例如,考虑Conduit
文档如何描述sinkList
:
从流中获取所有值并作为列表返回。请注意,这会将所有值都拉入内存。
这意味着在sinkMany
带您回到原点1之后立即使用yieldMany
:将所有值放入内存正是使sequence
,IO
和无限组合的原因列表无法使用。您应该做的是使用流式库的基础结构来构建管道的各个阶段。以下是一些使用 conduit 和 conduit-combinators 的现成产品的简单示例:
GHCi> import Conduit
GHCi> runConduitPure $ yieldMany [1..] .| takeC 5 .| sinkList
[1,2,3,4,5]
GHCi> runConduit $ yieldMany [1..] .| takeC 5 .| printC -- try it without takeC
1
2
3
4
5
GHCi> runConduit $ yieldMany [1..] .| takeC 5 .| scanlC (+) 0 .| printC
0
1
3
6
10
15
GHCi> :{
GHCi| runConduit $ yieldMany [1..] .| takeC 5
GHCi| .| awaitForever (\x -> liftIO (print (2*x)) >> yield x)
GHCi| .| printC
GHCi| :}
2
1
4
2
6
3
8
4
10
5
GHCi> runConduit $ (sourceRandom :: Producer IO Int) .| takeC 5 .| printC
1652736016140975126
5518223062916052424
-1236337270682979278
8079753510915129274
-609160753105692151
(感谢Michael让我注意到sourceRandom
- 起初我用repeatMC randomIO
推出了自己的推文。)