为什么要执行更高阶的程序?
因此,如果一种语言提供了更高阶的过程,那么我可以拥有返回过程的过程。 例如:
(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
可以使用不支持高阶过程的语言来完成类似的任务,方法是定义采用 4 个而不是 3 个参数的 Proc
并调用它定义 ProcA
的过程,例如:
(define (Proc a b c x) ( #| method body -- does not return any procedure |# )
(define (ProcA x) (Proc a1 b1 c1 x))
那么为什么高阶过程有这么多模糊之处? 我错过了什么吗?
So if a language provides higher order procedure then I can have procedure that returns procedure. Something like:
(define (Proc a b c)
(lambda (x) ( #| method body here in terms of a b c and x |# )))
To create new procedure, I would just do something like:
(define ProcA (Proc a1 b1 c1)) ; Would create ProcA that has 1 argument
Similar task could be done in a language which does not support higher order procedure by defining Proc
that takes 4 instead of 3 arguments and calling this procedure to define ProcA
, like:
(define (Proc a b c x) ( #| method body -- does not return any procedure |# )
(define (ProcA x) (Proc a1 b1 c1 x))
So why is there so much fuzz about higher order procedure? Am I missing something?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
一个很好的观察是,返回另一个函数的函数与带有两个参数的函数是相同的。 这称为“柯里化”。 换句话说,从 A 到 B 的函数是逻辑蕴含的证明,即 A 蕴含 B,或者:
正如您所注意到的,如果 A 蕴含 B 蕴含 C,那么 A 和 B 蕴含 C,或者:
但是一个高阶函数不一定是返回另一个函数的函数。 高阶函数是以另一个函数作为参数的函数。 这是一个重要的区别,HOF 是非常强大的编程工具。
例如,考虑这个 Haskell 函数:
这个高阶函数采用函数
f
并将其应用于列表中的每个元素。 在没有 HOF 的语言中,您可以使用循环或类似的东西来执行此函数的操作,但在具有 HOF 的语言中,您可以通过像这样的简单调用来为列表中的每个元素调用f
:当然,语言中的控制结构可以让您近似高阶函数,但是具有高阶函数的语言可以让您发明自己的控制结构。 方案当然符合条件。
It's a good observation that a function that returns another function is the same as a function that takes two arguments. This is called "Currying". Put another way, a function from A to B is proof of a logical implication, that A implies B, or:
As you note, if A implies that B implies C, then A and B implies C, or:
But a higher order function is not necessarily a function that returns another function. A higher-order function is a function that takes another function as its argument. This is an important difference, and HOFs are immensely powerful programming tools.
For example, consider this Haskell function:
This higher-order function takes a function
f
and applies it to every element in a list. In languages without HOFs, you would do what this function does with a loop or something similar, but in a language that has HOFs, you can callf
for every element in the list with a simple call like this:Sure, control constructs in languages let you approximate higher-order functions, but a language that has higher-order functions lets you invent your own control constructs. Scheme certainly qualifies.
我不会在这里重述这个论点,而是在 为什么函数式编程很重要,John Hughes 认为高阶函数很有用,因为它们提供了更有效的方法将程序的各个部分“粘合在一起”,从而使代码的重用变得更加容易。 这些示例使用的是一种非常古老的语言,不再被广泛使用,但它们仍然很容易理解并且非常有说服力。 阅读约翰的论文是详细回答您的问题“为什么高阶过程有这么多模糊之处”的好方法。
I won't try to recapitulate the argument here, but in Why Functional Programming Matters, John Hughes argues that higher-order functions are useful because they provide more effective ways to "glue together" parts of a program, and thereby they make it easier to reuse code. The examples are in a very old language that is no longer used much, but they are still easy to follow and pretty convincing. Reading John's paper is a good way to get a detailed answer to your question "why is there so much fuzz about higher-order procedures".
这更多的是心态而不是可行性。 它允许您将函数视为一等公民,并根据对函数进行操作以创建其他函数的函数进行思考。
显然,您可以使用其他语言来执行或模拟此操作,但如果它不是语法机制,则它会被视为作为补充或破解。
This is more about mindset than feasibility. It allows you to treat functions as first-class citizens and think in terms of functions that operate on functions to create other functions, etc.
Obviously you could do or simulate this with other languages, but if it's not a syntactic mechanism it's kind of treated as an addition or a hack.
好的,但在第二个示例中,您将在编译时使用预先指定的
a1
、b1
和c1
。 在第一个示例中,您在调用ProcA
时在运行时创建它,并且您可以根据需要创建任意多个不同的,这样您就可以做更多有趣的事情。OK, but in the second example, you're creating that procedure at compile time with a pre-ordained list of
a1
,b1
, andc1
. In the first example, you're creating it at runtime when you callProcA
, and you can create as many different ones as you please, so you can do much more interesting things.考虑通过数组的变换函数或排序算法。 现在,您希望使其变得非常灵活,以便让函数的用户通过让他们传递函数作为参数来指定函数的行为。
假设您使用以下过程原型编写一个排序算法:
该函数的用户可以通过传递适当的 fn 来指定他们是否想要降序或升序。
Think of a transform function or a sorting algorithm through an array. Now, you want to make it really flexible as to let the user of your function to specify the behaviour of your function by letting them pass a function as an argument.
Say, you write a sorting algorithm with the following procedural prototype:
The user of that function could specify, by passing the appropriate fn, if they want a descending or ascending ordering.
您需要一个内部类来正确模拟它。 第一种情况,Proc 对 a、b 和 c 是封闭的。 在第二种情况下,ProcA的调用者无法控制a1、b1和c1如何传递给另一个过程,他只能控制x。 因此,控制 a1、b1 和 c1 的方式是通过更高范围(模块级别或类似级别)的 use 变量,这使得您的函数不纯粹。 在这种情况下,您无法确保在调用之间给定相同的参数,ProcA 将返回相同的结果。 与 Proc 一样,您始终可以确定,如果使用相同的参数调用它,就会发生相同的结果。
You would need an inner-class to properly simulate that. The first case, Proc is closed over a, b and c. In the second case, the caller of ProcA cannot control how a1, b1 and c1 are passed to the other procedure, he can only control x. So, the way you control a1, b1 and c1 are through the use variables at a higher scope (module level or some such), which makes your function not pure. In that case, you cannot ensure that given the same arguments across calls, ProcA will return the same result. Where as with Proc, you can always be sure that if you call it with the same arguments, the same results will happen.
我在 javascript 中使用高阶函数,例如,当我使用选择框时。 我可以传入选择选项时将调用的函数,因为对我来说唯一的区别是,它简化了我的代码,减少了冗余。
我在我使用的支持高阶函数的其他语言中看到了同样的事情,因为我可以开始研究如何清理我的代码,其中存在一些可以本地化的冗余,并且任何差异都可以在功能。
一旦 C# 支持这一点,我就知道它现在更加主流了。 :)
I use higher-order functions in javascript, for example, when I use a select box. I can pass in the function that will be called when an option is selected, as the only difference for me was that, which simplifies my code, it reduces redundancy.
I see the same thing in other languages I use that supports higher-order functions, as I can then start to look at how to clean up my code, where there is some redundancy that can be localized, and any differences can be done in a function.
Once C# supported this, I knew it was now more mainstream. :)
如果函数接受和/或返回函数,则称为高阶函数(霍夫)。 对于来自 C、C++ 或 Java 的缺乏经验的程序员来说,高阶函数听起来很神奇,但它们非常简单。 想象一个返回 2 + 3 结果的简单函数:
这是一个无聊的函数,它总是将 2 与 3 相加。如果我们对其进行概括,使其不仅将 2 与 3 相加,而且将 2 与 3 相加,还会与任何用户提供的数字相加?
当一种语言不支持高阶函数时,您被迫认为函数和值(例如数字、布尔值、列表)是两个不同的东西。 但是函数式编程(FP)模糊了它们之间的区别。 想象一下,函数和值之间的唯一区别是函数可以被调用,除此之外,您可以对函数执行任何对
2
或#t
或'(abc)
:您可以将其作为参数给出,或从函数返回,或存储在变量中,或将其放入列表中。 例如,让我们进一步概括我们的小函数,这样它不仅可以将 2 加到n
,还可以将 2 乘以n
,或者应用任何其他接受两个数字的函数:当您意识到可以像处理数字或字符串一样处理函数时,匿名函数< /a> (在 FP 术语中称为“lambdas”)完全有道理。 匿名函数实际上比普通的命名函数更基本和“正常”,命名函数只是放入变量中的匿名函数,就像我们将一个数字放入变量中以便多次使用它一样。
因此 HOF 允许我们概括我们的功能,使它们变得超级灵活。 如果您查看您的函数,查看其背后的逻辑,您就会意识到,如果某些东西对您的数据进行操作,那么其他东西也可能对您的数据进行操作。 如果将两个数字加在一起,您可能可以将它们相乘、相减、或对另一个数字求幂,等等。 您可以只接受一个附加参数,该参数必须是一个函数,而不是每次都为每种情况编写一个新函数。
在 FP 中,我们一直使用 HOF,例如在操作列表时。 3 个函数是 FP 的基础:
地图
,过滤
和折叠
。map
接受一个带有 1 个参数的函数,将该函数应用于列表中的每个元素,并返回一个包含已更改元素的新列表。filter
接受带有 1 个参数的谓词(返回布尔值的函数),将该谓词应用于列表中的每个元素,并返回一个新列表,其中删除了不满足谓词的元素。想象一下,您有一个 1 元函数的列表 - 同样,您可以对函数执行任何您想要的操作,并将其存储在数据结构中 - 并且您希望将它们全部应用于同一个数字,并获得一个列表结果。
结论:当编程语言正确支持函数式编程概念时,高阶函数具有灵活性和通用性,这使您的代码更加强大(您可以针对各种用例使用相同的函数)并且简洁(无需为一个函数编写 10 个版本)。 一些高阶函数在函数式编程中大量使用,因此您可以摆脱低级且冗长的 for 循环,并编写可以完成所有操作的单行代码。
注意:
foldl
与“左折叠”或“左缩减”相同,功能更强大。 如果你真的感兴趣并且有时间,请阅读我使用reduce的回答的前半部分。 虽然它不是为Scheme/Racket 编写的,而是为Common Lisp/Emacs Lisp 编写的,但您仍然可以理解fold/reduce 背后的想法。If a function accepts and/or returns a function, it is called a higher-order function (HOF). For inexperienced programmers, coming from C, C++, or Java, higher-order functions sound like magic, but they are very simple. Imagine a simple function that returns the result of 2 + 3:
That's a boring function, it always adds 2 to 3. What if we generalize it, so that it adds 2 not only to 3, but to any user-provided number?
When a language doesn't support higher-order functions, you are forced to think that functions and values (e.g. numbers, booleans, lists) are 2 distinct things. But functional programming (FP) blurs distinction between them. Imagine that the only difference between a function and a value is that a function can be called, other than that you can do to a function whatever you could to a
2
or#t
or'(a b c)
: you could give it as an argument, or return from a function, or store in a variable, or put it in a list. For example, let's generalize our little function further, so not only it could add 2 ton
, but multiply 2 byn
, or apply any other function that would accept two numbers:When you realize that a function can be treated the same way a number or a string is treated, anonymous functions (called “lambdas” in FP jargon) make complete sense. Anonymous functions are actually more basic and “normal” than ordinary named functions, named functions are just anonymous functions put into a variable, just like we put a number into a variable to use it multiple times.
So HOFs allow us to generalize our functions to make them super-flexible. If you look at your function, see the logic behind it, you can realize, that if something operates on your data, then something else could probably too. If you add 2 numbers together, you could probably multiply them, or subtract, or exponentiate one to another, or whatever. Instead of writing a new function for every case each time, you could just accept an additional parameter, which has to be a function.
In FP we use HOFs all the time, for example, when manipulating lists. 3 functions are bread and butter of FP:
map
,filter
andfoldl
.map
accepts a function with 1 argument, applies this function to every element of a list, and returns a new list with changed elements.filter
accepts a predicate (function that returns a boolean) with 1 argument, applies the predicate to every element of a list, and returns a new list with elements that do not satisfy the predicate removed.Imagine, you have a list of 1-arity functions—again, you can do whatever you want with a function, and store it in a data structure too—and you want to apply all of them to the same number, and get a list of results.
Conclusion: when a programming language properly supports functional programming concepts, higher-order functions allow flexibility and generality, which makes your code both more powerful (you can use the same function for various use-cases) and concise (no need to write 10 versions of one function). Some higher-order functions are used heavily in functional programming, so you get rid of low-level and verbose for-loops and write one-liners that do everything instead.
Note:
foldl
, which is the same as “left fold” or “left reduce”, is even more powerful. If you're really interested and have time, please read the first half of my answer using reduce. While it isn't written for Scheme/Racket, but instead for Common Lisp/Emacs Lisp, you can still understand the idea behind fold/reduce.