我问了一个关于curry和闭包的问题。 什么是闭包?它和咖喱有什么关系?


当前回答

下面的示例演示了Scheme编程语言中的闭包。

首先定义一个函数,该函数定义了一个局部变量,在函数外部不可见。

; Function using a local variable
(define (function)
  (define a 1)
  (display a) ; prints 1, when calling (function)
  )
(function) ; prints 1
(display a) ; fails: a undefined

下面是同样的例子,但是现在函数使用了一个全局变量,定义在函数外部。

; Function using a global variable
(define b 2)
(define (function)
  (display b) ; prints 2, when calling (function)
  )
(function) ; prints 2
(display 2) ; prints 2

最后,这里有一个函数携带自己闭包的例子:

; Function with closure
(define (outer)
  (define c 3)
  (define (inner)
    (display c))
  inner ; outer function returns the inner function as result
  )
(define function (outer))
(function) ; prints 3

其他回答

从Lua.org:

当一个函数被封装在另一个函数中编写时,它可以完全访问封装函数中的局部变量;这个特性称为词法作用域。尽管这听起来很明显,但事实并非如此。词法作用域加上第一类函数是编程语言中一个强大的概念,但很少有语言支持这个概念。

首先,与这里大多数人告诉你的相反,闭包不是一个函数!那么它是什么呢? 它是在函数的“周围上下文”(称为其环境)中定义的一组符号,使其成为CLOSED表达式(即,每个符号都被定义并具有值的表达式,因此可以对其求值)。

例如,当你有一个JavaScript函数:

function closed(x) {
  return x + 3;
}

它是一个封闭表达式,因为其中出现的所有符号都在其中定义(它们的含义是明确的),所以您可以对其求值。换句话说,它是独立的。

但如果你有一个这样的函数

function open(x) {
  return x*y + 3;
}

它是一个开放的表达式,因为其中有尚未定义的符号。也就是y,当我们看这个函数的时候,我们不知道y是什么,它意味着什么,我们不知道它的值,所以我们不能计算这个表达式。也就是说,我们不能调用这个函数,直到我们知道y在其中的含义。这个y叫做自由变量。

这需要一个定义,但这个定义不是函数的一部分——它是在其他地方定义的,在它的“周围上下文”(也称为环境)中。至少这是我们所希望的:P

例如,它可以被全局定义:

var y = 7;

function open(x) {
  return x*y + 3;
}

或者它可以定义在一个包装它的函数中:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

环境中赋予表达式中自由变量意义的部分是闭包。之所以这么叫,是因为它通过为所有自由变量提供这些缺失的定义,把一个开放表达式变成一个封闭表达式,这样我们就可以求值了。

在上面的例子中,内部函数(我们没有给出名称,因为我们不需要它)是一个开放表达式,因为其中的变量y是自由的——它的定义在函数之外,在包装它的函数中。这个匿名函数的环境是一组变量:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

闭包是这个环境的一部分,它通过提供所有自由变量的定义来关闭内部函数。在我们的例子中,内部函数中唯一的自由变量是y,所以这个函数的闭包是它环境的这个子集:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

在环境中定义的另外两个符号不是该函数闭包的一部分,因为它不需要运行它们。他们不需要关闭它。

这里有更多的理论: https://stackoverflow.com/a/36878651/434562

It's worth to note that in the example above, the wrapper function returns its inner function as a value. The moment we call this function can be remote in time from the moment the function has been defined (or created). In particular, its wrapping function is no longer running, and its parameters which has been on the call stack are no longer there :P This makes a problem, because the inner function needs y to be there when it is called! In other words, it requires the variables from its closure to somehow outlive the wrapper function and be there when needed. Therefore, the inner function has to make a snapshot of these variables which make its closure and store them somewhere safe for later use. (Somewhere outside the call stack.)

And this is why people often confuse the term closure to be that special type of function which can do such snapshots of the external variables they use, or the data structure used to store these variables for later. But I hope you understand now that they are not the closure itself – they're just ways to implement closures in a programming language, or language mechanisms which allows the variables from the function's closure to be there when needed. There's a lot of misconceptions around closures which (unnecessarily) make this subject much more confusing and complicated than it actually is.

如果您来自Java世界,您可以将闭包与类的成员函数进行比较。看看这个例子

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

函数g是一个闭包:g封闭a。g可以和成员函数比较,a可以和类域比较,函数f可以和类比较。

下面是另一个现实生活中的例子,使用了游戏中流行的脚本语言——Lua。我需要稍微改变一个库函数的工作方式,以避免stdin不可用的问题。

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

当这段代码完成它的作用域时,old_dofile的值就消失了(因为它是本地的),但是该值已经被封装在一个闭包中,所以新的重新定义的dofile函数可以访问它,或者更确切地说,作为“upvalue”与函数一起存储的副本。

凯尔的回答很好。我认为唯一需要澄清的是,闭包基本上是lambda函数创建时堆栈的快照。然后,当函数重新执行时,堆栈将恢复到执行函数之前的状态。因此,正如Kyle提到的,当lambda函数执行时,隐藏值(count)是可用的。