问题Listing all the contents of a directory by breadth-first order results in low efficiency我了解到效率低是由于递归monad函数的奇怪行为。
尝试
sequence $ map return [1..]::[[Int]]
sequence $ map return [1..]::Maybe [Int]
和ghci将陷入无休止的计算中。
如果我们以一种更易读的形式重写序列函数,如下所示:
sequence' [] = return []
sequence' (m:ms) = do {x<-m; xs<-sequence' ms; return (x:xs)}
并尝试:
sequence' $ map return [1..]::[[Int]]
sequence' $ map return [1..]::Maybe [Int]
我们得到同样的情况,无休止的循环。
尝试有限列表
sequence' $ map return [1..]::Maybe [Int]
经过很长时间的等待后,它会弹出预期的结果Just [1,2,3,4..]
。
根据我们的尝试,我们可以得出这样的结论:尽管序列的定义似乎是懒惰的,但它是严格的,并且必须在序列结果之前得出所有数字才能打印出来。
不仅仅是序列',如果我们定义一个函数
iterateM:: Monad m => (a -> m a) -> a -> m [a]
iterateM f x = (f x) >>= iterateM0 f >>= return.(x:)
并尝试
iterateM (>>=(+1)) 0
然后发生无休止的计算。
众所周知,非monadic迭代的定义与上面的iterateM类似,但为什么迭代是惰性的,而iterateM是严格的。 我们可以从上面看到,iterateM和sequence'都是递归的monadic函数。有一些奇怪的递归monadic函数
答案 0 :(得分:14)
问题不在于sequence
的定义,而是基础monad的操作。特别是,monad的>>=
操作的严格性决定了sequence
的严格性。
对于一个足够懒惰的monad,完全可以在无限列表上运行sequence
并逐步使用结果。考虑:
Prelude> :m + Control.Monad.Identity
Prelude Control.Monad.Identity> runIdentity (sequence $ map return [1..] :: Identity [Int])
,列表将根据需要逐步打印(消耗)。
使用Control.Monad.State.Strict
和Control.Monad.State.Lazy
:
-- will print the list
Prelude Control.Monad.State.Lazy> evalState (sequence $ map return [1..] :: State () [Int]) ()
-- loops
Prelude Control.Monad.State.Strict> evalState (sequence $ map return [1..] :: State () [Int]) ()
在IO
monad中,>>=
的定义是严格的,因为这种严格性恰好是启用效果排序推理所必需的属性。我认为@ jberryman的答案很好地证明了“严格>>=
”的含义。对于IO
和其他具有严格>>=
的monad,必须在sequence
返回之前评估列表中的每个表达式。使用无限的表达式列表,这是不可能的。
答案 1 :(得分:13)
你并不是很熟悉绑定的机制:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
这是一个仅适用于3长度列表的序列实现:
sequence3 (ma:mb:mc:[]) = ma >>= (\a-> mb >>= (\b-> mc >>= (\c-> return [a,b,c] )))
在我们可以返回外部构造函数(即最外层的构造函数,或(:)
)之前,您看到我们如何“运行”列表中的每个“monadic动作”?如果您不相信,请尝试以不同方式实施。
这是monad对IO有用的一个原因:当你绑定两个动作时,会有一个隐含的效果排序。
您还必须小心使用“懒惰”和“严格”这两个术语。对于sequence
来说,你必须在最终结果被包装之前遍历整个列表,但是下面的工作非常好:
Prelude Control.Monad> sequence3 [Just undefined, Just undefined, Nothing]
Nothing
答案 2 :(得分:11)
Monadic sequence
一般无法在无限列表上懒散地工作。考虑一下它的签名:
sequence :: Monad m => [m a] -> m [a]
它将其参数中的所有monadic效果组合成单一效果。如果将其应用于无限列表,则需要将无限多个效果合并为一个。对于一些单子,有可能,对于一些单子,它不是。
例如,请考虑sequence
专门针对Maybe
,就像您在示例中所做的那样:
sequence :: [Maybe a] -> Maybe [a]
如果数组中的所有元素都是Just ...
,则结果为Just ...
。如果任何元素为Nothing
,则结果为Nothing
。这意味着除非您检查输入的所有元素,否则无法判断结果是Nothing
还是Just ...
。
同样适用于sequence
专门针对[]
:sequence :: [[a]] -> [[a]]
。如果参数的任何元素是空列表,则整个结果是空列表,如sequence [[1],[2,3],[],[4]]
中所示。因此,为了评估列表列表中的sequence
,您必须检查所有元素以查看结果的样子。
另一方面,专门用于Reader
monad的序列可以懒惰地处理它的参数,因为Reader
的monadic计算没有真正的“效果”。如果你定义
inf :: Reader Int [Int]
inf = sequence $ map return [1..]
或者
inf = sequence $ map (\x -> reader (* x)) [1..]
它会懒惰地工作,正如您可以通过调用take 10 (runReader inf 3)
看到的那样。