使用foldr定义长度

时间:2012-07-11 04:01:42

标签: haskell

我试图理解我正在上课的讲义中的一部分。它将长度函数定义为:

length = foldr (\_ n -> 1 + n) 0

有人可以解释一下这是如何运作的吗?我无法理解它。

3 个答案:

答案 0 :(得分:22)

首先,foldr的类型:(a -> b -> b) -> b -> [a] -> b

将用法纳入上下文,foldr接受3个参数:一个函数(接受 a。列表元素和 b。累加器,并返回累加器),累加器的起始值和列表。 foldr在通过列表应用函数后返回累加器的最终结果。

至于这段代码:

length = foldr (\_ n -> 1 + n) 0

正如您所看到的,它缺少列表 - 因此右侧的返回值是一个函数,它将获取一个列表并生成一个Int(与0相同的类型)。输入:[a] -> Int

右侧是什么意思:(\_ n -> 1 + n) 0

\表示声明一个未命名的函数

_表示忽略列表中的元素(对应a类型中的foldr)。如您所知,foldr将遍历列表并将函数应用于每个元素。这是传递给函数的元素。我们在length函数中没有使用它,所以我们表示它应该被忽略。

n是作为累加器传入的Int的参数。

->表示返回

1 + n将递增累加器。您可以想象返回值会传回foldr,而foldr会保存该值以传递给函数(\_ n -> 1 + n)的下一次调用。

括号外的0是计数器的起始值。

答案 1 :(得分:8)

函数foldr是使用右关联运算符折叠列表,如果使用运算符(+),则可以很容易地理解函数的作用,(该函数与{{}具有相同的行为1}}):

sum

对于长度函数,它相当于:

foldr (+) 0 [1,2,3,4,5] = 1+(2+(3+(4+(5+0))))

这就是foldr (\_ n -> 1 + n) 0 [1,2,3,4,5] = 1+(1+(1+(1+(1+0))))

的内容

答案 2 :(得分:5)

有几种方法可以理解它。第一个:foldr f z [1, 2, 3, 4, ..., n]计算以下值:

f 1 (f 2 (f 3 (f 4 (f ... (f n z)))))

所以在你的情况下:

length [1,2,3,4] = foldr (\_ n -> 1 + n) 0 [1,2,3,4]  
                 = (\_ n -> 1 + n) 1 ((\_ n -> 1 + n) 2 ((\_ n -> 1 + n) 3 ((\_ n -> 1 + n) 4 0)))
                 = (\_ n -> 1 + n) 1 ((\_ n -> 1 + n) 2 ((\_ n -> 1 + n) 3 (1 + 0)))
                 = (\_ n -> 1 + n) 1 ((\_ n -> 1 + n) 2 (1 + (1 + 0)))
                 = (\_ n -> 1 + n) 1 (1 + (1 + (1 + 0)))
                 = 1 + (1 + (1 + (1 + 0)))
                 = 1 + (1 + (1 + 1))
                 = 1 + (1 + 2)
                 = 1 + 3
                 = 4

另一个是从这个复制列表的函数开始:

listCopy :: [a] -> [a]
listCopy [] = []
listCopy (x:xs) = x : listCopy xs

这可能看起来像一个简单的函数,但foldr基本上就是这样,但除了将空列表[]和对构造函数:硬编码到右侧之外,我们而是使用一些任意常量和作为参数提供的函数。我有时想称这些参数fakeConsfakeNilconsnil:运算符和[]常量的名称。 Lisp语言),因为从某种意义上说,你是“复制”列表但使用伪构造函数:

foldr fakeCons fakeNil [] = fakeNil
foldr fakeCons fakeNil (x:xs) = fakeCons x (subfold xs)
    where subfold = foldr fakeCons fakeNil

所以根据这种解释,你的length函数正在“复制”一个列表,除了它使用0代替空列表,而不是:它丢弃元素和在运行总计中加1。

这是foldr f z xs的第三个解释:

    当列表为空时,
  1. z是您问题的解决方案。
  2. f是一个带有两个参数的函数:列表的一个元素,以及一个部分解决方案:问题的解决方案,用于显示在右侧的元素列表传递给f的元素。 f然后生成一个“更大的元素”的解决方案。
  3. 所以在length

    的情况下
    1. 空列表的长度为0,因此您使用0作为foldr的第二个参数。
    2. 如果xs的长度为n,则x:xs的长度为n+1。这就是你foldr\_ n -> n + 1的第一个参数正在做的事情:它计算列表的长度,作为参数给出列表的第一个元素(在这种情况下我们忽略)和长度列表的其余部分(n)。
    3. 这种对foldr的思考方式非常强大,不应低估。基本上,在您作为foldr的第一个参数传递的函数中,您可以假设您尝试解决的问题已经解决了所有比您正在处理的列表更短的列表。 。你的所有参数函数都要做的就是为一个元素计算一个元素的答案。