更新:好的,这个问题可能变得非常简单。

q <- mapM return [1..]

为什么这永远不回来?

mapM是否不会懒惰地处理无限列表?

下面的代码挂起。但是,如果我用B线代替A线,它将不再挂起。另外,如果我在行A前面加上“splitRandom $”,它也不会挂起。

Q1是:mapM不懒惰吗?否则,为什么用行B替换行A可以“修复此”代码?

Q2是:为什么前面带有splitRandom的行“解决”问题?
import Control.Monad.Random
import Control.Applicative

f :: (RandomGen g) => Rand g (Double, [Double])
f = do
    b <- splitRandom $ sequence $ repeat $ getRandom
    c <- mapM return b -- A
    -- let c = map id b -- B
    a <- getRandom
    return (a, c)

splitRandom :: (RandomGen g) => Rand g a -> Rand g a
splitRandom code = evalRand code <$> getSplit

t0 = do
    (a, b) <- evalRand f <$> newStdGen
    print  a
    print (take 3 b)

该代码懒惰地生成无限数量的随机数。然后,它生成一个随机数。通过使用splitRandom,我可以先在无限列表之前评估后一个随机数。如果我在函数中返回b而不是c可以证明这一点。

但是,如果我将mapM应用于列表,则程序现在挂起。为防止这种情况挂起,我必须在mapM之前再次应用splitRandom。我的印象是mapM可以懒惰

最佳答案

好吧,有一个懒惰,然后有一个懒惰。 mapM确实是懒惰的,因为它所做的工作并不多。但是,请查看类型签名:

mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]

考虑一下这意味着什么:给它提供一个函数a -> m b和一堆a。常规的map可以将它们变成一堆m b,但不能变成m [b]。在没有monad妨碍的情况下将b组合成单个[b]的唯一方法是使用>>=m b排序在一起以构造列表。

实际上,mapMsequence . map完全等效。

通常,对于任何一元表达式,如果根本不使用该值,则必须强制导致该表达式的整个>>=链,因此无法将sequence应用于无限列表。

如果要使用无限制的单调序列,则需要显式的流控制-例如,以某种方式烘焙到绑定(bind)链中的循环终止条件,而mapMsequence这样的简单递归函数则无法提供-或分步执行的顺序,如下所示:
data Stream m a = Nil | Stream a (m (Stream m a))

...这样您就可以仅根据需要强制使用多个monad层。

编辑:: 关于splitRandom,正在发生的事情是,您将它传递给Rand计算,并使用种子splitRandom进行评估,然后得到return来计算结果。没有splitRandom,单个getRandom所使用的种子必须来自对无限列表进行排序的最终结果,因此将其挂起。使用额外的splitRandom,只需通过两个splitRandom调用即可使用所使用的种子,因此它可以工作。最终的随机数列表之所以有用,是因为您此时已离开了Rand monad,并且没有任何内容取决于其最终状态。

关于haskell - Haskell的mapM不偷懒吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/3270255/

10-10 18:25