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


当前回答

不包含自由变量的函数称为纯函数。

包含一个或多个自由变量的函数称为闭包。

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read # leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

其他回答

变量作用域

声明局部变量时,该变量具有作用域。一般来说,局部变量只存在于声明它们的块或函数中。

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

如果我试图访问一个局部变量,大多数语言都会在当前作用域中查找它,然后向上遍历父作用域,直到到达根作用域。

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

当一个块或函数被处理完,它的局部变量就不再需要了,通常会耗尽内存。

这就是我们通常期望的事情的运作方式。

闭包是一个持久的局部变量作用域

闭包是一个持久作用域,即使在代码执行已经移出该块之后,它仍然保留局部变量。支持闭包的语言(如JavaScript、Swift和Ruby)将允许您保留对作用域(包括其父作用域)的引用,即使在声明这些变量的块已经完成执行之后,只要您在某处保留对该块或函数的引用。

作用域对象及其所有局部变量都绑定到函数,只要函数存在,它们就会存在。

这给了我们函数可移植性。我们可以预期,在函数第一次定义时在作用域中的任何变量,在稍后调用该函数时仍然在作用域中,即使我们在完全不同的上下文中调用该函数。

例如

下面是一个非常简单的JavaScript示例,可以说明这一点:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

这里我定义了一个函数中的函数。内部函数可以访问外部函数的所有局部变量,包括a。变量a在内部函数的作用域内。

正常情况下,当一个函数退出时,它的所有局部变量都会消失。但是,如果我们返回inner函数并将其赋值给变量fnc,以便在outer退出后它仍然存在,那么定义inner时在作用域内的所有变量也将仍然存在。变量a已经被关闭了——它在一个闭包中。

注意变量a对于fnc是完全私有的。这是在函数式编程语言(如JavaScript)中创建私有变量的一种方式。

正如你可能猜到的那样,当我调用fnc()时,它会打印a的值,即“1”。

在没有闭包的语言中,当函数outer退出时,变量a将被垃圾收集并丢弃。调用fnc将抛出一个错误,因为a不再存在。

在JavaScript中,变量a持续存在,因为变量作用域是在函数第一次声明时创建的,并且只要函数继续存在,变量a就会持续存在。

A属于outer的范围。inner的作用域有一个父指针指向outer的作用域。FNC是一个指向内的变量。只要FNC存在,A就存在。A在闭包内。

进一步阅读(观看)

我制作了一个YouTube视频,介绍了这段代码的一些实际使用示例。

下面的示例演示了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

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

下面是另一个现实生活中的例子,使用了游戏中流行的脚本语言——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”与函数一起存储的副本。

我将给出一个例子(JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...

这个makeCounter函数的作用是,它返回一个函数,我们称之为x,每次调用它都会加1。由于我们没有为x提供任何参数,它必须以某种方式记住计数。它知道根据所谓的词法作用域在哪里找到它——它必须查找定义值的位置才能找到值。这个“隐藏”值就是所谓的闭包。

下面还是我用咖喱的例子:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);
    
add3(4); returns 7

您可以看到,当您使用参数a(即3)调用add时,该值包含在我们定义为add3的返回函数的闭包中。这样,当我们调用add3时,它知道在哪里找到a值来执行加法。