3

我有一个f :: [a] -> b在无限列表上运行的函数(例如take 5takeWhile (< 100) . scanl (+) 0等等)。我想为这个函数提供由严格的一元动作(例如randomIO)生成的值。

这个问题中,我了解到repeatandsequence技巧方法不适用于严格的单子,如下面的示例所示:

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

所以,相反,我考虑在单子上下文“内部”使用函数。我受到这个循环编程示例的启发并尝试:

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

因此我想知道:

  1. 为什么上面的“循环”方法都不起作用?

  2. 如果存在不涉及的解决方案unsafeInterleaveIO。(也许iteratees,Arrows?)

4

2 回答 2

4

我在这里使用 randomIO 只是为了简化示例。在实践中,我想处理通过套接字接收到的消息

没有unsafeInterleaveIO. _ 归根结底,问题是IO必须按顺序执行操作。虽然引用透明值的评估顺序无关紧要,但IO操作的顺序可能。如果您想要通过套接字接收到的所有消息的惰性无限列表,您必须先验地通知 Haskell 在IO操作序列中适合的位置(除非您使用unsafeInterleaveIO)。

Arrow您正在寻求的抽象被称为 ,但对于严格的单子ArrowLoop来说,它也存在正确的紧缩法则。

乍一看,它可能看起来MonadFix(通过mdoor表现出来mfix)也是一个解决方案,但深入挖掘会发现fixIO存在问题,就像loopfrom一样ArrowLoop

但是,有时必须一个接一个地运行动作的限制IO有点过分,这就是unsafeInterleaveIO目的。引用文档

unsafeInterleaveIO允许IO延迟计算。当传递一个 type 的值时IO aIO只有在需要 的值时才会执行a


现在,即使您明确表示您想要一个unsafeInterleaveIO解决方案,因为我希望能够说服您这是要走的路,这里是:

interweavingRepeatM :: IO a -> IO [a]
interweavingRepeatM action = unsafeInterleaveIO ((:) <$> action <*> interweavingRepeatM action)

这里适用于随机数:

ghci> import System.Random
ghci> sourceOfRandomness <- interweavingRepeatM randomIO :: IO [Integer]
ghci> take 10 sourceOfRandomness
[-2002742716261662204,7803971943047671004,-8395318556488893887,-7372674153585794391,5906750628663631621,6428130029392850107,6453903217221537923,-8966011929671667536,6419977320189968675,-1842456468700051776]
于 2017-02-11T06:40:27.353 回答
3

亚历克的回答涵盖了你的一般问题。下面具体介绍管道和类似的流媒体库。

我还尝试了Conduit一种即使在这种情况下也不起作用的方法Identity

import Conduit

runConduitPure $ yieldMany [1..] .| sinkList >>= return . take 5

虽然流式库通常用于避免您提到的那种困难(参见 的开场白Pipes.Tutorial),但它们假定您将使用它们的流类型而不是列表。例如,考虑一下文档sinkList是如何描述的Conduit

使用流中的所有值并作为列表返回。请注意,这会将所有值拉入内存。

这意味着sinkMany在之后立即使用yieldMany会使您回到第一方:将所有值放入内存正是使sequence,IO和无限列表的组合无法使用的原因。相反,您应该做的是使用流库的基础架构来构建管道的各个阶段。这里有几个简单的例子,主要使用了管道管道组合器中的现成东西:

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

(感谢迈克尔让我注意到sourceRandom——起初我自己用repeatMC randomIO.)

于 2017-02-12T03:22:49.027 回答