Здесь много путаницы, потому что есть термины с несколькими определениями и множеством разных вещей, которые смешиваются просто потому, что они обычно встречаются вместе.
Во-первых, у нас есть «блок». Это просто лексическая часть кода, которая составляет единицу - например, тело цикла. Если язык на самом деле имеет область видимости блока, то можно определить переменные, которые существуют только в этом фрагменте кода.
Во-вторых, у нас есть вызываемый код как тип значения. В функциональных языках это значения функций - иногда называемые «funs», «anonymous functions» (потому что функция находится в значении, а не в имени, которому она назначена; вам не нужно имя для их вызова) или « лямбды »(от оператора, использовавшегося для их создания в церковном лямбда-исчислении). Их можно назвать «замыканиями», но они не являются автоматически истинными замыканиями; для квалификации они должны инкапсулировать («закрыть») лексическую область, окружающую их создание, то есть переменные, определенные вне области самой функции, но в рамках ее определения, по-прежнему доступны при каждом вызове функции, даже если вызывающая точка находится после указанной переменной, в противном случае она вышла бы из области видимости и использовала бы хранилище повторно.
Например, рассмотрим этот Javascript:
function makeClosure() {
var x = "Remember me!";
return function() {
return "x='" + x + "'";
}
}
// console.log(x);
// The above is an error; x is undefined
var f = makeClosure();
console.log(f());
// The above outputs a string that includes x as it existed when f was created.
Переменная x
определяется только внутри тела функции makeClosure
; вне этого определения его не существует. После того, как мы позвоним makeClosure
, объявленный внутри x
должен исчезнуть. И это, с точки зрения большей части кода. Но функция, возвращаемая makeClosure
, была объявлена в то время, когда существовала x
, поэтому она по-прежнему имеет доступ к ней, когда вы вызовете ее позже. Это делает его настоящим закрытием.
Вы можете иметь значения функций, которые не являются замыканиями, потому что они не сохраняют область видимости. Вы также можете иметь частичное закрытие; Значения функций PHP сохраняют только определенные переменные, которые должны быть перечислены в момент создания значения.
Вы также можете иметь вызываемые значения кода, которые вообще не представляют целые функции. Smalltalk называет эти «закрытия блоков», в то время как Ruby называет их «процессами», хотя многие Rubyists просто называют их «блоками», потому что они являются версией того, что создано {
... }
или * 1026. * ... end
синтаксис. Что отличает их от лямбд (или «замыканий функций»), так это то, что они не вводят новый уровень вызовов. Если код в теле закрытия блока вызывает return
, он возвращает из внешней функции / метода, в котором существует замыкание блока, а не только сам блок.
Такое поведение имеет решающее значение для сохранения того, что Р.Д. Теннент назвал «принципом соответствия», в котором говорится, что вы должны иметь возможность заменить любой код встроенной функцией, содержащей этот код в теле, и вызываться немедленно. Например, в Javascript вы можете заменить это:
с этим:
(function(){x = 2;})();
console.log(x)
Этот пример не очень интересен, но способность выполнять такого рода преобразования, не влияя на поведение программы, играет ключевую роль в функциональном рефакторинге. Но с лямбдами, как только вы ввели операторы return
, принцип больше не действует:
function foo1() {
if (1) {
return;
}
console.log("foo1: This should never run.")
}
foo1()
function foo2() {
if (1) {
(function() { return; })();
}
console.log("foo2: This should never run.")
}
foo2()
Вторая функция отличается от первой; console.log
выполняется, потому что return
возвращается только из анонимной функции, а не из foo2
. Это нарушает принцип соответствия.
Именно поэтому в Ruby есть как пробы, так и лямбды, хотя это различие является постоянным источником путаницы для новичков. И procs, и lambdas являются объектами класса Proc
, но они ведут себя по-разному, как указано выше: a return
просто возвращается из тела лямбды, но возвращается из метода, окружающего proc.
def test
p = proc do return 1 end
l = lambda do return 1 end
r = l[]
puts "Called l, got #{r}, still here."
r = p[]
puts "Called p, got #{r}, still here?"
end
Вышеуказанный метод test
никогда не перейдет ко второму puts
, потому что вызов p
приведет к немедленному возвращению test
(со значением возврата 1). Если в Javascript есть закрытие блоков, вы можете сделать то же самое, но это не так (хотя есть предложение добавить их).