使用严格的monad操作无限列表

时间:2017-02-11 05:32:49

标签: haskell monads lazy-evaluation conduit strictness

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

this question开始,我了解到repeatsequence技巧方法不适用于严格的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

因此我想知道:

  1. 为什么没有&#34;循环&#34;接近上述工作?

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

2 个答案:

答案 0 :(得分:4)

  

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

没有unsafeInterleaveIO是不可能的。一天结束时的问题是必须对IO个动作进行排序。虽然对引用透明值的评估顺序无关紧要,但IO行为的评估顺序可能无关紧要。如果你想要通过套接字接收的所有消息的惰性无限列表,你必须先通知Haskell在IO动作的顺序中哪个适合(除非你使用unsafeInterleaveIO)。

您正在寻找的Arrow抽象被称为ArrowLoop,但它对于严格的monad 正确的紧缩法也有问题。

乍一看,它可能看起来像MonadFix(通过mdomfix表现出来)也是一种解决方案,但挖掘得更深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:将所有值放入内存正是使sequenceIO和无限组合的原因列表无法使用。您应该做的是使用流式库的基础结构来构建管道的各个阶段。以下是一些使用 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推出了自己的推文。)