5

因此,如果一种语言提供更高阶的过程,那么我可以拥有返回过程的过程。就像是:

(define (Proc a b c)
  (lambda (x) ( #| method body here in terms of a b c and x |# )))

要创建新程序,我只需执行以下操作:

(define ProcA (Proc a1 b1 c1)) ; Would create ProcA that has 1 argument

类似的任务可以在不支持高阶过程的语言中完成,方法是定义Proc接受 4 个而不是 3 个参数并调用此过程来定义ProcA,例如:

(define (Proc a b c x) ( #| method body -- does not return any procedure |# )
(define (ProcA x) (Proc a1 b1 c1 x))

那么为什么有这么多关于高阶程序的模糊呢?我错过了什么吗?

4

8 回答 8

10

一个很好的观察结果是返回另一个函数的函数与接受两个参数的函数相同。这被称为“柯里化”。换句话说,从 A 到 B 的函数是逻辑蕴涵的证明,即 A 蕴涵 B,或者:

A => B.

正如您所注意到的,如果 A 暗示 B 暗示 C,那么 A 和 B 暗示 C,或者:

(A => (B => C)) <==> ((A, B) => C)

但高阶函数不一定是返回另一个函数的函数。高阶函数是将另一个函数作为其参数的函数。这是一个重要的区别,HOF 是非常强大的编程工具。

例如,考虑这个 Haskell 函数:

map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : (map f xs)

这个高阶函数接受一个函数f并将其应用于列表中的每个元素。在没有 HOF 的语言中,您可以使用循环或类似方法执行此函数的操作,但在具有 HOF 的语言中,您可以f通过如下简单调用来调用列表中的每个元素:

map f myList

当然,语言中的控制结构可以让您逼近高阶函数,但是具有高阶函数的语言可以让您发明自己的控制结构。方案当然符合条件。

于 2009-05-07T03:36:38.467 回答
3

我不想在这里重述这个论点,但在为什么函数式编程很重要,John Hughes 认为高阶函数很有用,因为它们提供了更有效的方法来“粘合”程序的各个部分,从而使程序更容易重用代码。这些示例使用的是一种非常古老的语言,不再使用太多,但它们仍然很容易理解并且很有说服力。阅读 John 的论文是详细回答您的问题“为什么对高阶过程有这么多模糊”的好方法。

于 2009-05-07T03:24:01.303 回答
1

这更多是关于心态而不是可行性。它允许您将函数视为一等公民,并根据对函数进行操作以创建其他函数等的函数进行思考。

显然你可以用其他语言来做或模拟它,但如果它不是一种句法机制,它就会被视为一种添加或破解。

于 2009-05-07T02:47:47.240 回答
1

好的,但是在第二个示例中,您在编译时使用预先指定的 、 和 列表创建a1b1过程c1。在第一个示例中,您在调用时在运行时创建它,ProcA您可以根据需要创建任意数量的不同的,因此您可以做更多有趣的事情。

于 2009-05-07T02:51:56.780 回答
1

通过数组考虑转换函数或排序算法。现在,您想让函数的用户真正灵活地通过让他们将函数作为参数传递来指定函数的行为。

假设您使用以下程序原型编写了一个排序算法:

sort(Array a, void (*fn)(a::element_type, a::element_type));

该函数的用户可以通过传递适当的 fn 来指定他们想要降序还是升序。

于 2009-05-07T03:13:52.250 回答
0

您需要一个内部类来正确模拟它。第一种情况,Proc 在 a、b 和 c 上是封闭的。在第二种情况下,ProcA 的调用者无法控制 a1、b1 和 c1 如何传递给另一个过程,他只能控制 x。因此,您控制 a1、b1 和 c1 的方式是通过在更高范围(模块级别等)上使用变量,这使您的函数不纯。在这种情况下,您无法确保在调用中给定相同的参数,ProcA 将返回相同的结果。与 Proc 一样,您始终可以确定,如果您使用相同的参数调用它,将会发生相同的结果。

于 2009-05-07T03:54:14.010 回答
0

我在 javascript 中使用高阶函数,例如,当我使用选择框时。我可以传入选择选项时将调用的函数,因为对我来说唯一的区别是,它简化了我的代码,它减少了冗余。

我在我使用的其他支持高阶函数的语言中看到了同样的东西,因为我可以开始研究如何清理我的代码,其中有一些可以本地化的冗余,并且任何差异都可以在一个功能。

一旦 C# 支持这一点,我就知​​道它现在更主流了。:)

于 2009-05-07T03:57:21.257 回答
0

如果一个函数接受和/或返回一个函数,则称为高阶函数(HOF)。对于来自 C、C++ 或 Java 的没有经验的程序员来说,高阶函数听起来很神奇,但它们非常简单。想象一个简单的函数,它返回 2 + 3 的结果:

(define (foo) (+ 2 3)) ;; (foo) => 5

这是一个无聊的函数,它总是将 2 加到 3。如果我们将其推广,让它不仅在 3 上加 2,而且在任何用户提供的数字上加 2?

(define (foo n) (+ 2 n)) ;; (foo 10) => 12

当一种语言不支持高阶函数时,你不得不认为函数和值(例如数字、布尔值、列表)是两个不同的东西。但是函数式编程(FP)模糊了它们之间的区别。想象一下,函数和值之间的唯一区别是函数可以被调用,除了你可以对函数做任何你可以对 a 2or #tor做的事情'(a b c):你可以把它作为一个参数,或者从一个函数返回,或存储在变量中,或将其放入列表中。例如,让我们进一步概括我们的小函数,因此它不仅可以将 2 添加到n,还可以将 2 乘以n,或者应用任何其他可以接受两个数字的函数:

(define (foo f n) (f 2 n))
;; (foo + 10) => 12
;; (foo * 10) => 20
;; (foo expt 10) => 1024

当您意识到可以以与处理数字或字符串相同的方式处理函数时,匿名函数(在 FP 术语中称为“lambdas”)就完全有意义了。匿名函数其实比普通的命名函数更基础、更“正常”,命名函数只是匿名函数放入一个变量中,就像我们将一个数字放入一个变量中多次使用一样。

(+ 2 2) ;; is no different from:
(let ((a 2)) (+ a a))
(lambda (x y) (* x y)) ;; is no different from:
(define (foo x y) (* x y)) ;; which is an abbreviation for:
(define foo (lambda (x y) (* x y))).

因此,HOF 允许我们泛化我们的函数以使其超级灵活。如果你看看你的函数,看看它背后的逻辑,你就会意识到,如果有什么东西对你的数据进行操作,那么其他东西可能也可以。如果将 2 个数字相加,您可能可以将它们相乘、相减或取幂,或其他任何方式。您可以只接受一个附加参数,而不是每次都为每种情况编写一个新函数,该参数必须是一个函数。

在 FP 中,我们一直使用 HOF,例如,在处理列表时。3 个函数是 FP 的基础mapfilterfoldlmap接受一个带 1 个参数的函数,将此函数应用于列表的每个元素,并返回一个包含更改元素的新列表。filter接受带有 1 个参数的谓词(返回布尔值的函数),将谓词应用于列表的每个元素,并返回一个新列表,其中删除了不满足谓词的元素。

(map (lambda (n) (+ n 1)) '(1 2 3 4 5) ;; '(2 3 4 5 6)
(define (foo n) (+ n 1))
(map foo '(1 2 3 4 5)) ;; '(2 3 4 5 6)
(filter (lambda (n) (> n 3)) '(1 2 3 4 5)) ;; '(4 5)
(define (bar n) (> n 3))
(filter bar '(1 2 3 4 5)) ;; '(4 5)

想象一下,你有一个 1-arity 函数的列表——同样,你可以对一个函数做任何你想做的事情,并将它也存储在一个数据结构中——并且你想将它们都应用于同一个数字,并得到一个列表结果。

(let ((xs (list (lambda (x) (+ x 1))
                (lambda (x) (* x 2))
                (lambda (x) (- x)))))
  (map (lambda (f) (f 10)) xs)) ;; => (11 20 -10)

结论:当一种编程语言正确地支持函数式编程概念时,高阶函数允许灵活性和通用性,这使您的代码既更强大(您可以将相同的函数用于各种用例)又简洁(无需编写 10 个版本一种功能)。一些高阶函数在函数式编程中被大量使用,所以你摆脱了低级和冗长的 for 循环,而是编写一个可以做所有事情的单行代码。

注意: foldl与“left fold”或“left reduce”相同,更强大。如果您真的有兴趣并且有时间,请使用 reduce 阅读我的答案的前半部分。虽然它不是为 Scheme/Racket 编写的,而是为 Common Lisp/Emacs Lisp 编写的,但您仍然可以理解 fold/reduce 背后的想法。

于 2014-12-26T12:44:23.627 回答