Haskell的mapM不是懒惰吗?

时间:2010-07-17 04:28:22

标签: haskell monads lazy-evaluation

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

q <- mapM return [1..]

为什么这永远不会回来?


mapM是不是懒得处理无限列表?

以下代码挂起。但是,如果我用线B替换A线,它就不会再挂起了。或者,如果我在A行之前加上“splitRandom $”,它也不会挂起。

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

Q2是:为什么在使用splitRandom的A行之前“解决”了这个问题?

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可以懒洋洋地

5 个答案:

答案 0 :(得分:31)

嗯,懒惰,然后 lazy mapM确实是懒惰的,因为它没有做更多的工作。但是,请查看类型签名:

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

想想这意味着什么:你给它一个函数a -> m b和一堆a。常规map可以将这些变为一堆m b,但不是m [b]。将b合并为单个[b]而不使monad妨碍的唯一方法是使用>>=m b进行排序以构建列表

事实上,mapM恰好等同于sequence . map

通常,对于任何monadic表达式,如果完全使用该值,则必须强制导致表达式的整个>>=链,因此将sequence应用于无限列表可以'永远都没有完成。

如果你想使用无界的monadic序列,你需要显式的流控制 - 例如,以某种方式绑定到绑定链中的循环终止条件,这些简单的递归函数如mapM和{ {1}}不提供 - 或一步一步的序列,如下所示:

sequence

...这样你就可以根据需要强制使用尽可能多的monad图层。

编辑:: 关于data Stream m a = Nil | Stream a (m (Stream m a)) ,正在进行的是您传递splitRandom计算,并使用种子Rand进行评估,然后splitRandom结果。如果没有return,单个splitRandom使用的种子必须来自无限列表排序的最终结果,因此它会挂起。使用额外的getRandom,使用的种子只需要通过两个splitRandom调用进行线程化,因此它可以工作。随机数的最终列表有效,因为你已经离开了splitRandom monad并且没有任何东西取决于它的最终状态。

答案 1 :(得分:7)

这是尝试mapM return [1..]未终止的证明。让我们暂时假设我们在Identity monad中(该论点同样适用于任何其他monad):

mapM return [1..] -- initial expression
sequence (map return [1 ..]) -- unfold mapM
let k m m' = m >>= \x ->
             m' >>= \xs ->
             return (x : xs)
in foldr k (return []) (map return [1..]) -- unfold sequence

到目前为止一直很好......

-- unfold foldr
let k m m' = m >>= \x ->
             m' >>= \xs ->
             return (x : xs)
    go [] = return []
    go (y:ys) = k y (go ys)
in go (map return [1..])

-- unfold map so we have enough of a list to pattern-match go:
go (return 1 : map return [2..])
-- unfold go:
k (return 1) (go (map return [2..])
-- unfold k:
(return 1) >>= \x -> go (map return [2..]) >>= \xs -> return (x:xs)

回想一下身份monad中的return a = Identity a和身份monad中的(Identity a) >>= f = f a。继续:

-- unfold >>= :
(\x -> go (map return [2..]) >>= \xs -> return (x:xs)) 1
-- apply 1 to \x -> ... :
go (map return [2..]) >>= \xs -> return (1:xs)
-- unfold >>= :
(\xs -> return (1:xs)) (go (map return [2..]))

请注意,此时我们很乐意申请\xs,但我们还不能!在我们有值申请之前,我们必须继续展开:

-- unfold map for go:
(\xs -> return (1:xs)) (go (return 2 : map return [3..]))
-- unfold go:
(\xs -> return (1:xs)) (k (return 2) (go (map return [3..])))
-- unfold k:
(\xs -> return (1:xs)) ((return 2) >>= \x2 ->
                         (go (map return [3..])) >>= \xs2 ->
                         return (x2:xs2))
-- unfold >>= :
(\xs -> return (1:xs)) ((\x2 -> (go (map return [3...])) >>= \xs2 ->
                        return (x2:xs2)) 2)

目前,我们仍无法申请\xs,但我们可以申请\x2。继续:

-- apply 2 to \x2 :
(\xs -> return (1:xs)) ((go (map return [3...])) >>= \xs2 ->
                         return (2:xs2))
-- unfold >>= :
(\xs -> return (1:xs)) (\xs2 -> return (2:xs2)) (go (map return [3..]))

现在我们已经达到了\xs \xs2都无法减少的程度!我们唯一的选择是:

-- unfold map for go, and so on...
(\xs -> return (1:xs))
  (\xs2 -> return (2:xs2))
    (go ((return 3) : (map return [4..])))

所以你可以看到,由于foldr,我们正在建立一系列要应用的功能,从列表末尾开始,然后重新开始工作。因为在每一步输入列表都是无限的,这种展开永远不会终止,我们永远不会得到答案。

如果你看一下这个例子(从另一个StackOverflow线程借来,我目前找不到哪一个),这是有意义的。在以下monad列表中:

mebs = [Just 3, Just 4, Nothing]

我们希望sequence能够抓住Nothing并为整个事情返回失败:

sequence mebs = Nothing

但是,对于此列表:

mebs2 = [Just 3, Just 4]

我们希望序列给我们:

sequence mebs = Just [3, 4]

换句话说,sequence 来查看整个monadic计算列表,将它们串在一起,并运行它们以便得出正确的答案。如果没有看到整个列表,sequence就无法给出答案。

注意:此答案的先前版本断言foldr从列表的后面开始计算,并且在无限列表上根本不起作用,但这是不正确的!如果传递给foldr的运算符在其第二个参数上是惰性的,并使用类似列表的惰性数据构造函数生成输出,foldr将很乐意使用无限列表。有关示例,请参阅foldr (\x xs -> (replicate x x) ++ xs) [] [1...]。但我们的运营商k并非如此。

答案 2 :(得分:7)

  

好的,这个问题可能非常简单。

     

q&lt; - mapM return [1 ..]

     

为什么这永远不会回来?

这不一定是真的。这取决于你所在的monad。

例如,使用标识monad,您可以懒惰地使用结果并终止正常:

newtype Identity a = Identity a

instance Monad Identity where
  Identity x >>= k = k x
  return = Identity

-- "foo" is the infinite list of all the positive integers
foo :: [Integer]
Identity foo = do
  q <- mapM return [1..]
  return q

main :: IO ()
main = print $ take 20 foo -- [1 .. 20]

答案 3 :(得分:3)

这个问题很好地展示了IO Monad和其他Monads之间的区别。在后台,mapM在所有列表元素之间构建一个带有绑定操作(&gt;&gt; =)的表达式,以将monadic表达式列表转换为列表的monadic表达式。现在,IO monad的不同之处在于Haskell的执行模型在IO Monad中的绑定期间执行表达式。这正是最终迫使(在一个纯粹的懒惰世界中)要执行的东西。

因此IO Monad在某种程度上是特殊的,它使用bind的序列范例来实际执行每一步的执行,这是我们的程序最终执行任何操作所做的。其他Monads是不同的。它们具有绑定运算符的其他含义,具体取决于Monad。 IO实际上是一个在绑定中执行事物的Monad,这就是为什么IO类型是“动作”的原因。

以下示例显示其他Monads不强制执行,例如Maybe monad。最后,这导致IO Monad中的mapM返回一个表达式,该表达式在执行时会在返回最终值之前执行每个单独的元素。

有关于此的好文章,从这里开始或搜索指称语义和Monads: 解决尴尬的小队:http://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/mark.pdf

可能Monad的例子:

模块主要位置

fstMaybe :: [Int] - &gt;也许[Int] fstMaybe = mapM(\ x - &gt;如果x == 3那么没有别的只是x)

main = do      让r = fstMaybe [1 ..]      返回r

答案 4 :(得分:1)

让我们在更通用的背景下讨论这个问题。

正如其他答案所说,mapM只是sequencemap的组合。所以问题是为什么sequence在某些Monad中是严格的。但是,这不仅限于Monads,还包括Applicative,因为我们sequenceA在大多数情况下共享sequence的相同实现。

现在看一下sequenceA的<专用于列表>类型签名:

sequenceA :: Applicative f => [f a] -> f [a]

你会怎么做?您获得了一个列表,因此您希望在此列表中使用foldr

sequenceA = foldr f b where ...
  --f :: f a -> f [a] -> f [a]
  --b :: f [a]

由于fApplicative,您知道b是什么 - pure []。但是什么是f? 显然它是(:)的升级版本:

(:) :: a -> [a] -> [a]

现在我们知道sequenceA如何运作:

sequenceA = foldr f b where
  f a b = (:) <$> a <*> b
  b = pure []

sequenceA = foldr ((<*>) . fmap (:)) (pure [])

假设您获得了一个惰性列表(x:_|_)sequenceA的上述定义给出了

sequenceA (x:_|_) === (:) <$> x <*> foldr ((<*>) . fmap (:)) (pure []) _|_
                  === (:) <$> x <*> _|_

现在我们看到问题已经减少,以考虑天气f <*> _|_是否为_|_。显然,如果f是严格的,那么_|_,但如果f不严格,要允许停止评估,我们要求<*>本身不严格。

因此,应用仿函数的标准是sequenceA停止运行 <*>运算符是非严格的。一个简单的测试就是

const a <$> _|_ === _|_      ====> strict sequenceA
-- remember f <$> a === pure f <*> a

如果我们谈论的是Moand,那么标准就是

_|_ >> const a === _|_ ===> strict sequence