带有foldr的Haskell递归函数示例

时间:2015-09-24 09:53:49

标签: haskell recursion lambda fold partial-application

我在短暂的中断之后再次学习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. 一个接受一个参数并返回布尔值的函数。由于>运算符实际上是一个中缀函数,上面示例中的第一个参数似乎是部分应用于>函数的结果 - 这是正确的吗?
  2. 与第一个参数
  3. 提供的函数的缺失参数相同类型的未指定值
  4. 上述类型的值列表
  5. 但实际函数firstThat似乎与其类型声明的定义不同,只有一个参数。由于foldr通常需要我收集的三个参数,因此会发生某种部分应用。作为foldr的参数提供的lambda表达式似乎也缺少其参数。

    那么,这个功能究竟是如何运作的?如果我太过茂密或者没有看到森林里的树木,我道歉,但我无法绕过它,这令人沮丧。

    非常感谢任何有用的解释或示例。

    谢谢!

2 个答案:

答案 0 :(得分:5)

  

一个接受一个参数并返回布尔值的函数。由于> operator实际上是一个中缀函数,上面例子中的第一个参数似乎是部分应用于>的结果。功能 - 这是正确的吗?

是的:(>10)\x -> x > 10的缩写,正如(10>)\x -> 10 > x的缩写。

  

与第一个参数

提供的函数的缺失参数相同类型的未指定值

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

  

上述类型的值列表

  

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

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

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

- 因为一个叫做currying的概念。 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.find

注意:您还可以将Data.Maybe.fromMaybeλ> fromMaybe 2000 $ find (>10) [10, 20, 30] 20 λ> fromMaybe 2000 $ find (>10) [1, 2, 3] 2000 结合使用:

    dialog.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
  public void onClick(DialogInterface dialog, int id) {
            mI.onClick();
  }
});

另见:

答案 1 :(得分:5)

  

但实际函数firstThat似乎与其类型声明的定义不同,只有一个参数。由于foldr通常需要我收集的三个参数,因此会发生某种部分应用。

你是对的。然而,有一种更好的方式来表达它,而不是谈论“缺失的论点” - 一个不会引导你去问他们去哪里。以下是两种不缺少参数的方法。

首先,考虑这个功能:

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所需的所有参数 - 即只有一个:)

  

作为foldr的参数提供的lambda表达式似乎也缺少其参数。

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

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

...所以传递给它的函数需要两个参数(根据上面的讨论,随意添加“两个”的空气引号)。 lambda写成......

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

...有两个明确的参数,xacc