Haskell:在do-notation中定义foldM

时间:2016-11-06 15:03:10

标签: haskell recursion bind

我对在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定义(>>=)一样?

1 个答案:

答案 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

我们可以将其与foldlfoldr与明确的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

TL; DR

您的单一课程材料没有实施foldM f a (x:xs) = do fax <- f a x foldM f fax xs ,因为它不是从左到右的评估,而是从右到左。从本质上讲,