【问题标题】:Racket - Closure / Currying, where is the difference?球拍 - 关闭/咖喱,有什么区别?
【发布时间】:2020-10-11 10:48:52
【问题描述】:

所以从我个人的研究来看,闭包/柯里化似乎或多或少完全相同,这显然不是正确的。那么区别在哪里呢?

以下是 Racket 中的闭包示例:

(define (make-an-adder x)
  (lambda (y)
    (+ y x)))

(define add3 (make-an-adder 3))


(add3 5)

会回馈

8

那么与柯里化有什么区别呢?因为如果我查找文档和其他示例,它们似乎与我在关闭时显示的完全相同?

提前谢谢大家!

【问题讨论】:

  • 我想说闭包是一种与范围相关的技术,它首先允许柯里化。柯里化体现了每个 lambda 表达式只有一个参数的数学思想。在这种设置中表达多参数函数的唯一方法是嵌套单参数函数。
  • 柯里化是一个概念/想法,闭包是实现技术/细节。

标签: functional-programming closures scheme racket currying


【解决方案1】:

所以它们是不同的概念,但都与嵌套的 lambdas 相关。

闭包可以由 lambda 创建,该 lambda 引用定义在自身外部的变量,并且当 lambda 从定义该外部变量的上下文中转义时最重要。闭包的工作是确保在 lambda 转义该上下文时保留该变量。

Curried 函数是一个可以在多个步骤或多个不同的函数应用程序中获取其参数的函数。这通常意味着 lambdas 中嵌套了 lambdas。

柯里化函数并不总是闭包,尽管它们通常是

大多数有用的柯里化函数都需要使用闭包,但如果内部 lambda 忽略外部参数,它们就不是闭包。一个简单的例子:

(define (curried-ignore-first ignored)
  (lambda (y) y))

这不是一个闭包,因为内部 lambda (lambda (y) y) 已经关闭:它不引用自身外部的任何变量。

柯里化函数并不总是需要忽略外部参数...它只需要在返回内部 lambda 之前完成处理它们,这样内部 lambda 就不会引用外部参数。一个简单的例子是 curried choose 函数。 choose 的“正常”定义确实使用了闭包:

(define (choose b)
  (lambda (x y)
    (if b x y))) ; inner lambda refers to `b`, so it needs a closure

但是,如果将if b 放在外部 lambda 之外,我们可以避免进行闭包:

(define (choose b)
  (if b
      (lambda (x y) x)   ; not closures, just nested lambdas
      (lambda (x y) y)))

闭包并不总是来自柯里化函数

当内部 lambda 引用外部上下文中的变量并可能转义该上下文时,需要闭包。外部上下文通常是函数或 lambda,但不一定是。它可以是一个让:

(define closure-with-let
  (let ([outer "outer"])
    (lambda (ignored) outer))) ; closure because it refers to `outer`

这是一个闭包,但不是柯里化的例子。

将 Curried-function-produce-a-closure 变成一个没有闭包的函数

原始问题中的示例是一个产生闭包的柯里化函数

(define (make-an-adder x)
  (lambda (y)
    (+ y x)))

如果您想创建一个仍然是具有相同行为的柯里化函数的版本,但在某些特殊情况下不需要关闭 x,您可以在 lambda 之前的那些上进行分支:

(define (make-an-adder x)
  (match x
    [0 identity]
    [1 add1]
    [-1 sub1]
    [2 (lambda (y) (+ y 2))]
    [3 (lambda (y) (+ y 3))]
    [_ (lambda (y) (+ y x))]))

这避免了在 x 是精确整数 -1 到 3 的情况下生成闭包,但在 x 的所有其他情况下仍会生成闭包。如果您将x 的域限制为有限集,您可以将其变成不需要闭包的函数,只需枚举所有情况即可。

如果您不希望在x 上进行闭包,但对其他事物的闭包没问题,您可以使用递归和组合来构造一个不会在x 上关闭的输出函数:

(define (make-an-adder x)
  (cond [(zero? x) identity]
        [(positive-integer? x)
         (compose add1 (make-an-adder (sub1 x)))]
        [(negative-integer? x)
         (compose sub1 (make-an-adder (add1 x)))]))

请注意,这仍然会产生闭包(因为compose 在其参数上创建了闭包),但它产生的函数不会在x 上关闭。一旦这个版本的make-an-adder 产生了它的结果,它就“完成”了处理x 并且不再需要关闭它了。

【讨论】:

  • 你的回答太棒了!一个问题:是否可以更新答案并举例说明我发布的示例如何看起来像柯里化?只是为了好的测量。只要不打扰你。已经谢谢你的回答了!已经帮了很多忙了!
  • 您问题中的示例已经是柯里化的示例和闭包的示例。柯里化来自能够在多个步骤中应用它,而闭包来自内部 lambda,引用外部函数中的 x
  • 好的,我绝对需要进一步深入研究您的答案。是否可以将我给定的示例转换为仅使用柯里化/闭包的示例?你已经给出了很好的例子,但我想知道我给出的例子会是什么样子。
  • 我不确定你的意思。我想您总是可以通过将所有外部 lambda 转换为 let 来将 currying+closure 示例转换为仅闭包示例,但这使得它的用处大大降低。一个更有趣的问题是,您是否可以将 currying+closure 示例转换为不需要闭包的等效 curried 函数......我的猜测是否定的,不是针对所有示例,而是某些示例,例如外部参数是否全部仅限于有限集,也许?我不知道
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-28
  • 2015-02-10
  • 1970-01-01
相关资源
最近更新 更多