Racket - Closure / Currying,区别在哪里?

Racket - Closure / Currying, where is the difference?

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

所以这里有一个 Racket 中闭包的例子:

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

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


(add3 5)

会回馈

8

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

先谢谢大家了!

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

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

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

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

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

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

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

柯里化函数并不总是需要忽略外部参数...它只需要在它之前处理它们 returns 内部 lambda,这样内部 lambda 就不会引用外部参数。一个简单的例子是柯里化 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,但不一定是。可以是 let:

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

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

将 Curried-function-producing-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 进行闭包,但对其他事情进行闭包也无妨,您可以使用递归和组合来构造一个不对 [= 进行闭包的输出函数=21=]:

(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 并且不再需要关闭它。