Haskell 带 foldr 的递归函数示例

Haskell recursive function example with foldr

在短暂的中断之后,我再次开始学习 Haskell,目前我正试图更好地理解递归和 lambda 表达式在 Haskell 中的工作原理。

在这个:YouTube video 中,有一个函数示例让我感到困惑,就其实际工作原理而言,它可能比它应该的更让我困惑:

firstThat :: (a -> Bool) -> a -> [a] -> a
firstThat f = foldr (\x acc -> if f x then x else acc)

为了清楚起见并且由于它对我来说不是很明显,我将举一个将此函数应用于某些参数的示例:

firstThat (>10) 2000 [10,20,30,40] --returns 20, but would return 2000, if none of the values in the list were greater than 10

如果我的假设有误,请指正。

似乎firstThat需要三个参数:

  1. 一个接受一个参数和 returns 一个布尔值的函数。由于 > 运算符实际上是一个中缀函数,因此上面示例中的第一个参数似乎是对 > 函数的部分应用的结果——这是正确的吗?
  2. 与作为第一个参数提供的函数的缺失参数预期相同类型的未指定值
  3. 上述类型的值列表

但实际函数 firstThat 的定义似乎与其类型声明不同,只有一个参数。由于 foldr 通常采用我收集到的三个参数,因此发生了某种部分应用程序。作为参数提供给 foldr 的 lambda 表达式似乎也缺少它的参数。

那么,这个功能究竟是如何工作的呢?如果我太密集或只见树木不见森林,我深表歉意,但我无法绕过它,这令人沮丧。

任何有用的解释或示例将不胜感激。

谢谢!

a function that takes one arguments and returns a Boolean value. Since the > operator is actually an infix function, the first argument in the example above seems the result of a partial application to the > function – is this correct?

是的:(>10)\x -> x > 10 的缩写,就像 (10>)\x -> 10 > x 的缩写一样。

an unspecified value of the same type expected as the missing argument to the function provided as the first argument

首先,它不是缺少参数:通过省略参数,您可以获得一个函数值。然而,第二个参数的类型确实匹配函数 >10 的参数,就像它匹配列表 [10,20,30,40] 的元素类型一样(这是更好的推理)。

a list of values of the aforementioned type

是的。

But the actual function firstThat seems to be defined differently from its type declaration, with just one argument. Since foldr normally takes three arguments I gathered there is some kind of partial application happening. The lambda expression provided as an argument to foldr seem to be missing its arguments too.

那是因为给出了例如foo x y z = x * y * z,这两行是等价的:

bar x     = foo x
bar x y z = foo x y z

— 这是因为一个叫做 currying 的概念。柯里化也是函数类型签名不是 (a, b) -> c 而是 a -> b -> c 的原因,后者又等价于 a -> (b -> c),因为 -> 类型运算符的正确结合性。

因此,这两行是等价的:

firstThat f     = foldr (\x acc -> if f x then x else acc)
firstThat f x y = foldr (\x acc -> if f x then x else acc) x y

注:您还可以将Data.List.findData.Maybe.fromMaybe结合使用:

λ> fromMaybe 2000 $ find (>10) [10, 20, 30]
20
λ> fromMaybe 2000 $ find (>10) [1, 2, 3]
2000

另请参阅:

But the actual function firstThat seems to be defined differently from its type declaration, with just one argument. Since foldr normally takes three arguments I gathered there is some kind of partial application happening.

你是对的。然而,有一种比谈论 "missing arguments" 更好的表达方式——一种不会让您问他们去哪里了的方式。下面介绍两种不漏参数的方法

首先考虑这个函数:

add :: Num a => a -> a -> a
add x y = x + y

你可能知道,我们也可以这样定义:

add :: Num a => a -> a -> a
add = (+)

之所以可行,是因为 Haskell 函数的值与其他函数一样。我们可以简单地定义一个值 add 等于另一个值 (+),它恰好是一个函数。声明函数不需要特殊的语法。结果是(几乎)从来没有必要明确地写论据;我们这样做的主要原因是它通常使代码更具可读性(例如,我可以定义 firstThat 而无需显式编写 f 参数,但我不会这样做,因为结果相当可怕).

其次,每当您看到带有三个参数的函数类型时...

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

...你也可以这样读...

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

... 即一个参数的函数产生两个参数的函数。这适用于多个参数的所有函数。关键要点是,从本质上讲,所有 Haskell 函数只接受一个参数 。这就是部分应用程序起作用的原因。所以一看到...

firstThat :: (a -> Bool) -> a -> [a] -> a
firstThat f = foldr (\x acc -> if f x then x else acc)

...您可以准确地说您已经显式编写了 firstThat 采用的所有参数——也就是说,只有一个 :)


The lambda expression provided as an argument to foldr seem to be missing its arguments too.

不是真的。 foldr(仅限于列表时)是...

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

... 因此传递给它的函数需要两个参数(考虑到上面的讨论,请随意在 "two" 周围添加空引号)。 lambda 写成...

\x acc -> if f x then x else acc

... 有两个显式参数,xacc.