Замыкание (программирование) | это... Что такое Замыкание (программирование)? (original) (raw)

У этого термина существуют и другие значения, см. Замыкание.

Замыкание (англ. closure) в программировании — процедура или функция, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции и не в качестве её параметров (а в окружающем коде). Говоря другим языком, замыкание — это процедура или функция, которая ссылается на свободные переменные в своём лексическом контексте.

Замыкание, так же как и экземпляр объекта, есть способ представления функциональности и данных, связанных и упакованных вместе.

Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. В записи это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.

В случае замыкания ссылки на переменные внешней функции действительны внутри вложенной функции до тех пор, пока работает вложенная функция, даже если внешняя функция закончила работу, и переменные вышли из области видимости.[1]

Замыкание связывает код функции с её лексическим окружением (местом, в котором она определена в коде). Лексические переменные замыкания отличаются от глобальных переменных тем, что они не занимают глобальное пространство имён. От переменных в объектах они отличаются тем, что привязаны к функциям, а не объектам.

Содержание

Реализации замыкания в языках программирования

Pascal

Пример работы замыканий на Pascal (Delphi c 2009 версии):

type TGenericFunction = reference to function: string;

function Factory(const ASomeText: string):TGenericFunction; begin Result := function: string begin Result := ASomeText; end; end;

var f1, f2: TGenericFunction;

procedure TForm1.Button1Click(Sender: TObject); begin f1 := Factory('First'); f2 := Factory('Second');

Memo1.Lines.Add(f1); Memo1.Lines.Add(f2); end;

В версиях начиная с 2009, этот код выведет в Memo строки First и Second. Когда переменной типа reference to *** присваивается совместимая по спецификации анонимная подпрограмма или метод, неявно создаётся и инициализируется экземпляр анонимного класса, с полями для хранения значений, используемых подпрограммой из контекста её объявления, методом выполнения (присвоенной подпрограммой) и счётчиком ссылок.

Scheme

Пример работы замыканий на Scheme:

(define (make-adder n) ; возвращает замкнутое лямбда-выражение (lambda (x) ; в котором x - связанная переменная, (+ x n))) ; а n - свободная (захваченная из внешнего контекста)

(define add1 (make-adder 1)) ; делаем процедуру для прибавления 1 (add1 10) ; печатает 11

(define sub1 (make-adder -1)); делаем процедуру для вычитания 1 (sub1 10) ; печатает 9

C#

Анонимные методы в C# 2.0 могут замыкаться на локальный контекст:

int[] ary = { 1, 2, 3 }; int x = 2; var ary1 = Array.ConvertAll<int, int>(ary, delegate(int elem) { return elem * x; }); // { 2, 4, 6 } // or.. var ary2 = Array.ConvertAll<int, int>(ary, elem => { return elem * x; }); // { 2, 4, 6 }

Функция Array.ConvertAll преобразует один список/массив в другой, применяя для каждого элемента передаваемую ей в качестве параметра функцию.

В C# 3.0 введены лямбда-выражения, которые делают синтаксис анонимных методов более кратким и выразительным. Соответственно, они также поддерживают замыкания. То есть, замыкания в C# 3.0 практически аналогичны анонимным функциям из C# 2.0, но синтаксически более кратки. Вот тот же пример с применением лямбда-выражений в C# 3.0:

int[] ary = { 1, 2, 3 }; var x = 2; var ary1 = ary.Select(elem => elem * x); // { 2, 4, 6 }

Метод Select аналогичен методу Array.ConvertAll за тем исключением, что он принимает и возвращает IEnumerable.

C++

В языке C++ замыкание долгое время не поддерживалось. Однако новый стандарт языка C++11 вводит лямбда-функции и выражения, ограниченно поддерживающие замыкание:

function<int()> f() { int x = 0; return [=] () mutable {return ++x; }; }

auto fun = f(); for (int i = 0; i < 5; ++i) { cout << fun() << endl; }

VB.NET

В VB.NET 9.0 лямбда-функции могут быть только однострочными. Начиная с версии 10.0, можно использовать синтаксис для описания многострочных лямбда-функций.

Dim ary As Integer() = {1, 2, 3} Dim x As Integer = 2

' VB.NET 9.0 - { 2, 4, 6 } Dim ary1() As Integer = Array.ConvertAll(Of Integer, Integer)(ary, Function(elem) elem * x)

' VB.NET 10.0 - { 2, 4, 6 } Dim ary2() As Integer = Array.ConvertAll(Of Integer, Integer)(ary, Function(elem) Return elem * x End Function)

Ruby

Некоторые языки, такие как Ruby, позволяют выбирать различные способы замыканий по отношению к оператору возврата return. Вот пример на Ruby:

ruby

def foo f = Proc.new { return "return from foo from inside proc" } f.call # после вызова функции замыкания f осуществляется выход из foo # результатом работы функции foo является результат работы f замыкания return "return from foo" end

def bar f = lambda { return "return from lambda" } f.call # после вызова функции замыкания f продолжается выполнение bar return "return from bar" end

puts foo # печатает "return from foo from inside proc" puts bar # печатает "return from bar"

И Proc.new, так же как и lambda, в этом примере — это способы создания замыкания, но семантика замыканий различна по отношению к оператору return.

PHP

PHP имеет встроенную поддержку замыканий начиная с версии 5.3. Пример замыкания. Локальная переменная $id будет увеличиваться при вызове возвращаемой функцией getAdder вложенной функции:

function getAdder() { $id = 1; return function() use (&$id){ // use (&$id) для того чтобы передать в возвращаемую функцию внешнюю переменную $id return $id++; }; }

$test= getAdder(); echo test();//1test(); //1 test();//1id увеличивается только после того, как возвращается, так как написано $id++ echo $test(); //2 echo $test(); //3 echo $test(); //4

Для более ранних версий возможно использовать одноименный шаблон проектирования, который реализуется в библиотеке Николаса Нассара. P.S. Однако, до сих пор существует проблема с замыканиями в классах, в частности — для статических методов класса.

Java

Java реализует концепцию замыкания с помощью анонимных классов. Анонимный класс имеет доступ к полям класса, в лексическом контексте которого он определён, а также к переменными с модификатором final в лексическом контексте метода.

class CalculationWindow extends JFrame { private JButton btnSave; ...

 public final void calculateInSeparateThread(final URI uri) {
     // Выражение "new Thread() { ... }" представляет собой пример анонимного класса.
     new Thread() {
         public void run() {
             // Имеет доступ к финальным (final) переменным:
             calculate(uri);
             // Имеет доступ к приватным членам содержащего класса:
             btnSave.setEnabled(true);
         }
     }.start();
 }

}

Предполагалось, что версия Java-7 будет включать полную поддержку концепции замыканий, которые официально должны были называться «лямбда-выражения» (Lambda expressions), но этого не произошло. Теперь поддержка «лямбда-выражений» заявлена в версии Java-8[2].

Python

Пример с использованием замыканий и карринга:

Реализация с помощью именованных функций:

def taskerize(func_object): def unbound_closure(*args, **kwarg): def bound_closure(): return func_object(*args, **kwarg)

    return bound_closure

return unbound_closure

Равносильная реализация с использованием lambda:

taskerize = lambda func_object: ( lambda *args, **kwarg: ( lambda: func_object(*args, **kwarg) ) )

@taskerize # применение декоратора равнозначно записи testfunc = taskerize(testfunc) после объявления функции. def testfunc(a, b, c): return a + b * c

f = testfunc(1, 2, 3) print f() # выведет 7

Пример простого замыкания:

Реализация с помощью именованных функций:

def make_adder(x): def adder(n): return x + n # захват переменной "x" из внешнего контекста return adder

То же самое, но через безымянные функции:

make_adder = lambda x: ( lambda n: ( x + n ) )

f = make_adder(10) print f(5) # 15 print f(-1) # 9

Пример карринга:

Функция с кучей аргументов (26 шт.), делающая что-то невразумительное.

def longfunc(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z): print 'Меня вызвали с такими аргументами: ', a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z return a + b * c - d / e + f / g - h * i - (j * (k - l) + m) + (n * o) / (p - q + r) + (s * (t + (u * (v + w)))) - (x * y * z)

def curry(func_object, *args): def innerfunc(local_args): # в функции выполняется замыкание на args и func_object из внешнего контекста return func_object((args + local_args)) # а еще нам нужно прилепить в конец тех аргументов, что у нас были, новые return innerfunc

По уже сложившейся традиции — то же самое, только лямбдами:

curry = lambda func_object, *args: ( lambda *local_args: ( func_object( *(args + local_args) ) ) )

"достраиваем" функцию, как пожелаем.

f1 = curry(longfunc, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100) f2 = curry(f1, 110, 120, 130, 140) f3 = curry(f2, 150, 160, 170, 180, 190, 200) f4 = curry(f3, 210)

не обязательно использовать функцию, к которой был применен карринг, только один раз.

f5 = curry(f4, 220, 230, 240, 250, 260) # раз f5b = curry(f4, 220, 230, 240, 250) # два! f6b = curry(f5b, 260)

print f5() # выведет 2387403 print f6b() # опять выведет 2387403

контроль того, что карринг всё сделал верно (вызываем функцию со всеми её 26-ю параметрами):

print longfunc( # перенос значений аргументов функций на несколько строк не имеет ничего общего с каррингом. Нет, правда. 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260 ) # да, опять выведет 2387403.

JavaScript

В JavaScript областью видимости локальных переменных (объявляемых словом var) является тело функции, внутри которой они определены.[3]

Если вы объявляете функцию внутри другой функции, первая получает доступ к переменным и аргументам последней:

function outerFn(myArg) { var myVar; function innerFn() { // имеет доступ к myVar и myArg } }

При этом, такие переменные продолжают существовать и остаются доступными внутренней функции даже после того, как внешняя функция, в которой они определены, была исполнена.

Рассмотрим пример — функцию, возвращающую количество собственных вызовов:

function createCounter() { var numberOfCalls = 0; return function() { return ++numberOfCalls; } } var fn = createCounter(); fn(); // 1 fn(); // 2 fn(); // 3

Perl

Пример с использованием замыканий на Perl:

возвращает анонимную функцию

sub adder{ my $x = shift(); # в котором x - свободная переменная, sub{ my $y = shift(); # а y - связанная переменная x+x + x+y; }; }

$add1 = adder(1); # делаем процедуру для прибавления 1 print $add1->(10); # печатает 11

$sub1 = adder(-1); # делаем процедуру для вычитания 1 print $sub1->(10); # печатает 9

Lua

Пример с использованием замыканий на Lua:

function makeaddfunc(x) -- Возвращает новую анонимную функцию, которая добавляет x к аргументу return function(y) -- Когда мы ссылаемся на переменную x, которая вне текущей области, -- и время жизни которой меньше, чем этой анонимной функции, -- Lua создаёт замыкание. return x + y end end plustwo = makeaddfunc(2) print(plustwo(5)) -- Выводит 7

Haskell

В Haskell замыкания используются повсеместно в виде частичного применения аргументов к функциям (также известного как каррирование).

sum3 :: Int -> Int -> Int -> Int sum3 x y z = x + y + z

Определение функции «sum3» напоминает следующий код на C:

int sum3(int x, int y, int z) { return(x + y + z); }

На самом деле «sum3» эквивалентна функции «sum3_desugared», по определению которой видно, что «sum3_desugared» принимает один аргумент «x» и возвращает новую функцию со связанной переменной «x». Новая функция также принимает только один аргумент «y» и возвращает функцию от одного аргумента «z».

sum3_desugared :: Int -> Int -> Int -> Int sum3_desugared = \x -> \y -> \z -> x + y + z

Псевдоопределение таких функций выглядит следующим образом («bounded» — это некоторые фиксированные значения, которые неявно хранятся вместе с функциями):

sum2_closure :: Int -> Int -> Int sum2_closure = \y -> \z -> bounded_from_sum3 + y + z

sum1_closure :: Int -> Int sum1_closure = \z -> bounded_from_sum3 + bounded_from_sum2 + z

sum_value :: Int sum_value = bounded_from_sum3 + bounded_from_sum2 + bounded_from_sum1

sum2_with42 = sum3 42 sum2_with42 = \y -> \z -> 42 + y + z

sum1_with42_with13 = sum3 42 13 sum1_with42_with13 = sum2_with42 13 sum1_with42_with13 = \z -> 42 + 13 + z

sum_with42_with13_with66 = sum3 42 13 66 sum_with42_with13_with66 = sum2_with42 13 66 sum_with42_with13_with66 = sum1_with42_with13 66 sum_with42_with13_with66 = 42 + 13 + 66

Такой подход очень часто применяется для создания «специализированных» функций из более общих:

-- (&&) :: Bool -> Bool -> Bool -- liftM2 :: (Monad m) => (a -> b -> c) -> m a -> m b -> m c

(<&&>) (Monad m) => m Bool -> m Bool -> m Bool (<&&>) = liftM2 (&&)

-- foldr :: (a -> b -> b) -> b -> [a] -> b

custom_fold :: [a] -> b custom_fold = foldr k z where z = {- zero value -} k x z = {- combining function -}

Для удобства использования общих функций рекомендуют располагать в них параметры в порядке от общих к частным. Например, сначала принимать функцию-обработчик перед самими данными.

Smalltalk

Пример с использованием замыкания на Smalltalk:

createClosureWithComparator: aComparator

^[ :each | ^ each < aComparator ]

Выполнение метода создает замыкание, при использовании которого будет происходить сравнение произвольного аргумента each и связанного значения aComparator.

MATLAB

Пример реализации замыкания в MATLAB с использованием nested функций:

function d = some_func(a)

function c = nested_func(b)
    c = a + b;
end

d = @nested_func;

end

f = some_func(10); f = @some_func/nested_func

f(5) ans = 15

Пример реализации замыкания в MATLAB с использованием анонимных функций:

f = @(x) @(y) x + y f = @(x)@(y)x+y

ff = f(10) ff = @(y)x+y

ff(5) ans = 15

Objective-C

Пример реализации замыкания в Objective-c с использованием блоков(blocks):

typedef int (^Add)(int x,int y);

Add addBlock = (^(int x, int y) {
return x + y; });

int res = addBlock(5,6); NSLog(@"%i",res);

11

Common LISP

(defun photon-energy-common (planck) (lambda (freq) (* planck freq)))

(setq photon-energy-hbar (photon-energy-common 1.054571726E-23))
(setq photon-energy-h (photon-energy-common 6.62606957E-23))

(funcall photon-energy-h 10E12)

Go

package main

import "fmt"

func fibonacci() func() int { a, b := 0, 1 return func() int { a, b = b, a + b return b } }

func main() { f := fibonacci() for i := 0; i < 10; ++i { fmt.Println(f()) } }

См. также

Примечания

  1. Blocks Can Be Closures — Containers, Blocks, and Iterators — Programming Ruby. The Pragmatic Programmer’s Guide.
  2. OpenJDK: Project Lambda
  3. Владимир Агафонкин Замыкания в JavaScript