【问题标题】:Haskell recursive function example with foldr带有 foldr 的 Haskell 递归函数示例
【发布时间】:2015-12-21 21:10:12
【问题描述】:

在短暂的中断之后,我再次开始学习 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. 上述类型的值列表

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

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

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

谢谢!

【问题讨论】:

  • 抱歉错别字,感谢编辑,duplode!
  • 顺便说一句,你的firstThat 没有任何递归——仅仅因为foldr 帮助你非递归地重写一个递归函数,并不意味着你应该引用任何使用的函数foldr 作为递归的。
  • 谢谢,我明白你的意思了,当然这是有道理的。我要学习的东西比我想象的要多。
  • 也许看到写成firstThat :: (a -> Bool) -> a -> ([a] -> a)的函数会有所帮助?它接受两个参数并返回一个[a] -> a

标签: haskell recursion lambda fold partial-application


【解决方案1】:

但实际函数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

【讨论】:

  • 在阅读了最后三分之一的答案后,它点击了。高声。我应该马上做一个:t foldr。这甚至可能对我有足够的帮助,让我意识到我将来应该更多地关注 lambdas。很好的答案,谢谢!
【解决方案2】:

一个接受一个参数并返回一个布尔值的函数。由于 > 运算符实际上是一个中缀函数,所以上例中的第一个参数似乎是对 > 函数的部分应用的结果——这样正确吗?

是的:(>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

——这是因为一个叫做柯里化的概念。柯里化也是函数类型签名不是(a, b) -> c而是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

另见:

【讨论】:

  • 我对部分应用和柯里化都很熟悉,但是你的回答仍然可以帮助我避免在未来被一些特定的概念绊倒。这就是为什么我也赞成你的分析器。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-14
  • 2019-02-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多