我对在do-block中定义foldM
感到困惑,主要是因为它的递归。 foldM
的标准定义如下:
foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM _ a [] = return a
foldM f a (x:xs) = f a x >>= \fax -> foldM f fax xs
我理解这个定义; f a x
的结果以递归方式传递给新的foldM
函数,直到列表为空。以下是do-block中foldM
的定义(从我的uni课程资料中复制):
foldM _ z [] = return z
foldM f z (x:xs) = do r <- foldM f z xs
f r x
我知道do块的定义基本上是绑定(>>=)
操作的语法糖。但是,我似乎无法理解这个定义的工作原理。我总觉得do-blocks中的递归非常令人困惑。给r
的是什么?每次递归调用r <- foldM f z xs
传递foldM
时,递归行z
如何正确?不应该传递递归更新的参数,例如f z x
,就像foldM
定义(>>=)
一样?
答案 0 :(得分:2)
后一个变量从右到左评估列表上的操作,而另一个变量从右到左评估列表上的操作。让我们称之为foldMDo
,以便我们可以将它们分开:
foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a
foldM _ a [] = return a
foldM f a (x:xs) = f a x >>= \fax -> foldM f fax xs
foldMDo _ z [] = return z
foldMDo f z (x:xs) = do r <- foldMDo f z xs
f r x
如果我们使用它们打印列表,差异很明显:
ghci> foldM (\_ y -> print y) () [1..10]
1
2
3
4
5
6
7
8
9
10
ghci> foldMDo (\_ y -> print y) () [1..10]
10
9
8
7
6
5
4
3
2
1
所以,让我们去试验do
变体:
do r <- foldMDo f z xs
f r x
与
相同foldMDo f z xs >>= \fzxs -> f fzxs x
让我们将它与彼此相邻的另一个进行比较:
(1) f a x >>= \fax -> foldM f fax xs
(2) foldMDo f z xs >>= \fzxs -> f fzxs x
我们可以将其与foldl
和foldr
与明确的let...in
进行比较:
foldr f a (x:xs) = let acc = foldr f a xs
in f a acc
foldl f a (x:xs) = let acc = f a x
in foldr f acc xs
uni材质看起来像foldr
,而默认材质看起来像foldl
,正如预期的评估顺序一样。
要完成,请添加do
的{{1}}版本:
foldM
您的单一课程材料没有实施foldM f a (x:xs) = do fax <- f a x
foldM f fax xs
,因为它不是从左到右的评估,而是从右到左。从本质上讲,