Encerramento (programação de computador) - Closure (computer programming)

Em linguagens de programação , um encerramento , também encerramento léxico ou encerramento de função , é uma técnica para implementar a vinculação de nomes com escopo léxico em uma linguagem com funções de primeira classe . Operacionalmente , um fechamento é um registro que armazena uma função junto com um ambiente. O ambiente é um mapeamento que associa cada variável livre da função (variáveis ​​que são usadas localmente, mas definidas em um escopo delimitador) com o valor ou referência ao qual o nome foi vinculado quando o encerramento foi criado. Ao contrário de uma função simples, um encerramento permite que a função acesse essas variáveis ​​capturadas por meio das cópias do encerramento de seus valores ou referências, mesmo quando a função é chamada fora de seu escopo.

História e etimologia

O conceito de closures foi desenvolvido na década de 1960 para a avaliação mecânica de expressões no cálculo λ e foi totalmente implementado em 1970 como um recurso de linguagem na linguagem de programação PAL para suportar funções de primeira classe com escopo léxico .

Peter J. Landin definiu o termo fechamento em 1964 como tendo uma parte de ambiente e uma parte de controle, conforme usado por sua máquina SECD para avaliar expressões. Joel Moses atribui a Landin a introdução do termo fechamento para se referir a uma expressão lambda cujas ligações abertas (variáveis ​​livres) foram fechadas pelo (ou vinculadas) ao ambiente lexical, resultando em uma expressão fechada ou fechamento. Este uso foi subsequentemente adotado por Sussman e Steele quando eles definiram Scheme em 1975, uma variante de escopo léxico do Lisp , e se tornou generalizado.

Sussman e Abelson também usam o termo fechamento na década de 1980 com um segundo significado não relacionado: a propriedade de um operador que adiciona dados a uma estrutura de dados para também ser capaz de adicionar estruturas de dados aninhadas. Esse uso do termo vem do uso da matemática, e não do uso anterior na ciência da computação. Os autores consideram essa sobreposição de terminologia "lamentável".

Funções anônimas

O termo fechamento é freqüentemente usado como sinônimo de função anônima , embora estritamente, uma função anônima seja uma função literal sem um nome, enquanto um fechamento é uma instância de uma função, um valor , cujas variáveis ​​não locais foram associadas a valores ou para locais de armazenamento (dependendo do idioma; consulte a seção de ambiente léxico abaixo).

Por exemplo, no seguinte código Python :

def f(x):
    def g(y):
        return x + y
    return g  # Return a closure.

def h(x):
    return lambda y: x + y  # Return a closure.

# Assigning specific closures to variables.
a = f(1)
b = h(1)

# Using the closures stored in variables.
assert a(5) == 6
assert b(5) == 6

# Using closures without binding them to variables first.
assert f(1)(5) == 6  # f(1) is the closure.
assert h(1)(5) == 6  # h(1) is the closure.

os valores de ae bsão fechamentos, em ambos os casos produzidos pelo retorno de uma função aninhada com uma variável livre da função envolvente, de modo que a variável livre se liga ao valor do parâmetro xda função envolvente. Os fechos em ae bsão funcionalmente idênticos. A única diferença na implementação é que no primeiro caso usamos uma função aninhada com um nome, genquanto no segundo caso usamos uma função anônima anônima (usando a palavra-chave Python lambdapara criar uma função anônima). O nome original, se houver, usado para defini-los é irrelevante.

Um fechamento é um valor como qualquer outro valor. Ele não precisa ser atribuído a uma variável e, em vez disso, pode ser usado diretamente, conforme mostrado nas duas últimas linhas do exemplo. Este uso pode ser considerado um "fechamento anônimo".

As definições de funções aninhadas não são em si fechamentos: eles têm uma variável livre que ainda não está vinculada. Somente quando a função envolvente é avaliada com um valor para o parâmetro é a variável livre do limite da função aninhada, criando um fechamento, que é então retornado da função envolvente.

Por último, um encerramento só é distinto de uma função com variáveis ​​livres quando fora do escopo das variáveis ​​não locais, caso contrário, o ambiente de definição e o ambiente de execução coincidem e não há nada para distingui-los (vinculação estática e dinâmica não pode ser distinguida porque os nomes resolvem para os mesmos valores). Por exemplo, no programa a seguir, funções com uma variável livre x(ligada à variável não local xcom escopo global) são executadas no mesmo ambiente onde xestá definido, portanto, é irrelevante se estes são realmente encerramentos:

x = 1
nums = [1, 2, 3]

def f(y):
    return x + y

map(f, nums)
map(lambda y: x + y, nums)

Isso é mais frequentemente alcançado por um retorno de função, uma vez que a função deve ser definida dentro do escopo das variáveis ​​não locais, caso em que normalmente seu próprio escopo será menor.

Isso também pode ser obtido pelo sombreamento de variável (que reduz o escopo da variável não local), embora isso seja menos comum na prática, pois é menos útil e o sombreamento é desencorajado. Neste exemplo, fpode ser visto como um encerramento porque xno corpo de festá vinculado ao xno namespace global, não ao xlocal a g:

x = 0

def f(y):
    return x + y

def g(z):
    x = 1  # local x shadows global x
    return f(z)

g(1)  # evaluates to 1, not 2

Formulários

O uso de encerramentos está associado a linguagens em que as funções são objetos de primeira classe , nos quais as funções podem ser retornadas como resultados de funções de ordem superior ou passadas como argumentos para outras chamadas de função; se as funções com variáveis ​​livres forem de primeira classe, retornar uma cria um encerramento. Isso inclui linguagens de programação funcionais , como Lisp e ML , bem como muitas linguagens multiparadigmáticas modernas, como Python e Rust . Os fechamentos também são frequentemente usados ​​com retornos de chamada , especialmente para manipuladores de eventos , como em JavaScript , onde são usados ​​para interações com uma página da web dinâmica .

Os fechamentos também podem ser usados ​​em um estilo de passagem de continuação para ocultar o estado . Construções como objetos e estruturas de controle podem, portanto, ser implementadas com fechamentos. Em algumas linguagens, um fechamento pode ocorrer quando uma função é definida dentro de outra função, e a função interna se refere a variáveis ​​locais da função externa. Em tempo de execução , quando a função externa é executada, um encerramento é formado, consistindo no código da função interna e referências (os upvalues) a quaisquer variáveis ​​da função externa exigidas pelo encerramento.

Funções de primeira classe

Fechamentos normalmente aparecem em linguagens com funções de primeira classe - em outras palavras, essas linguagens permitem que funções sejam passadas como argumentos, retornadas de chamadas de função, vinculadas a nomes de variáveis ​​etc., assim como tipos mais simples, como strings e inteiros. Por exemplo, considere a seguinte função Scheme :

; Return a list of all books with at least THRESHOLD copies sold.
(define (best-selling-books threshold)
  (filter
    (lambda (book)
      (>= (book-sales book) threshold))
    book-list))

Neste exemplo, a expressão lambda (lambda (book) (>= (book-sales book) threshold)) aparece dentro da função best-selling-books. Quando a expressão lambda é avaliada, Scheme cria um encerramento que consiste no código para a expressão lambda e uma referência à thresholdvariável, que é uma variável livre dentro da expressão lambda.

O fechamento é então passado para a filterfunção, que o chama repetidamente para determinar quais livros devem ser adicionados à lista de resultados e quais devem ser descartados. Como a própria closure tem uma referência threshold, ela pode usar essa variável sempre que a filterchamar. A filterprópria função pode ser definida em um arquivo completamente separado.

Aqui está o mesmo exemplo reescrito em JavaScript , outra linguagem popular com suporte para encerramentos:

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

A functionpalavra-chave é usada aqui em vez de lambda, e um Array.filtermétodo em vez de uma filterfunção global , mas caso contrário, a estrutura e o efeito do código são os mesmos.

Uma função pode criar um encerramento e retorná-lo, como no exemplo a seguir:

// Return a function that approximates the derivative of f
// using an interval of dx, which should be appropriately small.
function derivative(f, dx) {
  return function (x) {
    return (f(x + dx) - f(x)) / dx;
  };
}

Devido ao encerramento neste caso sobrevive a execução da função que o cria, as variáveis fe dxviver após a função derivativeretorna, embora a execução deixou seu âmbito e eles não são mais visíveis. Em linguagens sem encerramentos, o tempo de vida de uma variável local automática coincide com a execução do quadro de pilha onde essa variável é declarada. Em linguagens com encerramentos, as variáveis ​​devem continuar a existir, desde que quaisquer encerramentos existentes tenham referências a eles. Isso é mais comumente implementado usando alguma forma de coleta de lixo .

Representação estadual

Um encerramento pode ser usado para associar uma função a um conjunto de variáveis ​​" privadas ", que persistem por várias invocações da função. O escopo da variável abrange apenas a função fechada, portanto, não pode ser acessada a partir de outro código de programa. Elas são análogas às variáveis ​​privadas na programação orientada a objetos e, de fato, os encerramentos são análogos a um tipo de objeto , especificamente objetos de função , com um único método público (chamada de função) e, possivelmente, muitas variáveis ​​privadas (as variáveis ​​fechadas) .

Em linguagens com estado, fechamentos podem ser usados ​​para implementar paradigmas para representação de estado e ocultação de informações , uma vez que os upvalues ​​do fechamento (suas variáveis ​​fechadas) são de extensão indefinida , então um valor estabelecido em uma invocação permanece disponível na próxima. Fechamentos usados ​​dessa forma não têm mais transparência referencial e, portanto, não são mais funções puras ; no entanto, eles são comumente usados ​​em linguagens funcionais impuras, como Scheme .

Outros usos

Os fechos têm muitos usos:

  • Como os fechamentos atrasam a avaliação - ou seja, eles não "fazem" nada até serem chamados - eles podem ser usados ​​para definir estruturas de controle. Por exemplo, todas as estruturas de controle padrão de Smalltalk , incluindo branches (if / then / else) e loops (while e for), são definidas usando objetos cujos métodos aceitam fechamentos. Os usuários também podem definir facilmente suas próprias estruturas de controle.
  • Em linguagens que implementam atribuição, várias funções podem ser produzidas que se aproximam do mesmo ambiente, permitindo que eles se comuniquem de forma privada alterando esse ambiente. No esquema:
(define foo #f)
(define bar #f)

(let ((secret-message "none"))
  (set! foo (lambda (msg) (set! secret-message msg)))
  (set! bar (lambda () secret-message)))

(display (bar)) ; prints "none"
(newline)
(foo "meet me by the docks at midnight")
(display (bar)) ; prints "meet me by the docks at midnight"
  • Os fechamentos podem ser usados ​​para implementar sistemas de objetos .

Nota: Alguns falantes chamam qualquer estrutura de dados que vincula um ambiente léxico de encerramento, mas o termo geralmente se refere especificamente a funções.

Implementação e teoria

Fechamentos são normalmente implementados com uma estrutura de dados especial que contém um ponteiro para o código da função , mais uma representação do ambiente léxico da função (ou seja, o conjunto de variáveis ​​disponíveis) no momento em que o fechamento foi criado. O ambiente de referência vincula os nomes não locais às variáveis ​​correspondentes no ambiente léxico no momento em que o fechamento é criado, estendendo adicionalmente seu tempo de vida para pelo menos o tempo de vida do próprio fechamento. Quando o fechamento é inserido em um momento posterior, possivelmente com um ambiente lexical diferente, a função é executada com suas variáveis ​​não locais referentes às capturadas pelo fechamento, não o ambiente atual.

Uma implementação de linguagem não pode suportar facilmente fechamentos completos se seu modelo de memória de tempo de execução alocar todas as variáveis ​​automáticas em uma pilha linear . Nessas linguagens, as variáveis ​​locais automáticas de uma função são desalocadas quando a função retorna. No entanto, um encerramento requer que as variáveis ​​livres às quais ele faz referência sobrevivam à execução da função envolvente. Portanto, essas variáveis ​​devem ser alocadas para que persistam até que não sejam mais necessárias, normalmente por meio de alocação de heap , em vez de na pilha, e seu tempo de vida deve ser gerenciado para que sobrevivam até que todos os fechamentos que as referenciam não estejam mais em uso.

Isso explica por que, normalmente, as linguagens que oferecem suporte nativo a encerramentos também usam a coleta de lixo . As alternativas são o gerenciamento manual de memória de variáveis ​​não locais (alocando explicitamente no heap e liberando quando feito) ou, se estiver usando a alocação de pilha, para que a linguagem aceite que certos casos de uso levarão a um comportamento indefinido , devido a ponteiros pendentes para variáveis ​​automáticas liberadas, como em expressões lambda em C ++ 11 ou funções aninhadas em GNU C. O problema funarg (ou problema de "argumento funcional") descreve a dificuldade de implementar funções como objetos de primeira classe em uma linguagem de programação baseada em pilha, como C ou C ++. Da mesma forma, na versão D 1, assume-se que o programador sabe o que fazer com delegados e variáveis ​​locais automáticas, pois suas referências serão inválidas após o retorno de seu escopo de definição (as variáveis ​​locais automáticas estão na pilha) - isso ainda permite muitos padrões funcionais, mas para casos complexos precisa de alocação explícita de heap para variáveis. D versão 2 resolveu isso detectando quais variáveis ​​devem ser armazenadas no heap e realiza a alocação automática. Como D usa a coleta de lixo, em ambas as versões, não há necessidade de rastrear o uso de variáveis ​​à medida que são passadas.

Em linguagens estritamente funcionais com dados imutáveis ​​( por exemplo, Erlang ), é muito fácil implementar o gerenciamento automático de memória (coleta de lixo), pois não há ciclos possíveis nas referências das variáveis. Por exemplo, em Erlang, todos os argumentos e variáveis ​​são alocados na pilha, mas as referências a eles são armazenadas adicionalmente na pilha. Depois que uma função retorna, as referências ainda são válidas. A limpeza do heap é feita pelo coletor de lixo incremental.

Em ML, as variáveis ​​locais têm escopo léxico e, portanto, definem um modelo semelhante a uma pilha, mas, uma vez que são vinculadas a valores e não a objetos, uma implementação é livre para copiar esses valores para a estrutura de dados da closure de uma maneira que é invisível para o programador.

Scheme , que possui um sistema de escopo léxico semelhante ao ALGOL com variáveis ​​dinâmicas e coleta de lixo, carece de um modelo de programação em pilha e não sofre das limitações das linguagens baseadas em pilha. Os fechamentos são expressos naturalmente no esquema. A forma lambda envolve o código e as variáveis ​​livres de seu ambiente persistem dentro do programa, desde que possam ser acessadas e, portanto, podem ser usadas tão livremente quanto qualquer outra expressão de Scheme.

Fechamentos estão intimamente relacionados aos Atores no modelo Ator de computação simultânea, em que os valores no ambiente léxico da função são chamados de conhecidos . Uma questão importante para encerramentos em linguagens de programação simultâneas é se as variáveis ​​em um encerramento podem ser atualizadas e, em caso afirmativo, como essas atualizações podem ser sincronizadas. Os atores fornecem uma solução.

Fechamentos estão intimamente relacionados a objetos de função ; a transformação do primeiro para o último é conhecida como desfuncionalização ou levantamento lambda ; veja também conversão de fechamento .

Diferenças na semântica

Ambiente léxico

Como diferentes linguagens nem sempre têm uma definição comum do ambiente lexical, suas definições de encerramento também podem variar. A definição minimalista comumente aceita do ambiente léxico o define como um conjunto de todas as ligações de variáveis no escopo, e isso também é o que os fechamentos em qualquer linguagem precisam capturar. No entanto, o significado de uma ligação variável também é diferente. Em linguagens imperativas, as variáveis ​​se ligam a locais relativos na memória que podem armazenar valores. Embora a localização relativa de uma ligação não mude no tempo de execução, o valor na localização da ligação pode. Em tais linguagens, uma vez que o fechamento captura a ligação, qualquer operação na variável, seja feita a partir do fechamento ou não, é realizada no mesmo local relativo da memória. Isso geralmente é chamado de captura da variável "por referência". Aqui está um exemplo que ilustra o conceito em ECMAScript , que é uma dessas linguagens:

// ECMAScript
var f, g;
function foo() {
  var x;
  f = function() { return ++x; };
  g = function() { return --x; };
  x = 1;
  alert('inside foo, call to f(): ' + f());
}
foo();  // 2
alert('call to g(): ' + g());  // 1 (--x)
alert('call to g(): ' + g());  // 0 (--x)
alert('call to f(): ' + f());  // 1 (++x)
alert('call to f(): ' + f());  // 2 (++x)

Função fooe os fechamentos referidos por variáveis fe gtodos usam a mesma localização relativa da memória significada por variável local x.

Em alguns casos, o comportamento acima pode ser indesejável e é necessário ligar um fecho lexical diferente. Novamente no ECMAScript, isso seria feito usando o Function.bind().

Exemplo 1: Referência a uma variável não associada

var module = {
  x: 42,
  getX: function() {return this.x; }
}
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// emits undefined as 'x' is not specified in global scope.

var boundGetX = unboundGetX.bind(module); // specify object module as the closure
console.log(boundGetX()); // emits 42

Exemplo 2: referência acidental a uma variável ligada

Para este exemplo, o comportamento esperado seria que cada link emitisse seu id ao ser clicado; mas como a variável 'e' está limitada ao escopo acima, e lentamente avaliada no clique, o que realmente acontece é que cada evento no clique emite o id do último elemento em 'elementos' vinculados no final do loop for.

var elements= document.getElementsByTagName('a');
//Incorrect: e is bound to the function containing the 'for' loop, not the closure of "handle"
for (var e of elements){ e.onclick=function handle(){ alert(e.id);} }

Novamente aqui a variável eprecisaria ser limitada pelo escopo do bloco usando handle.bind(this)ou a letpalavra - chave.

Por outro lado, muitas linguagens funcionais, como ML , vinculam variáveis ​​diretamente a valores. Nesse caso, uma vez que não há como alterar o valor da variável depois de ligada, não há necessidade de compartilhar o estado entre os fechamentos - eles apenas usam os mesmos valores. Isso geralmente é chamado de captura da variável "por valor". As classes locais e anônimas do Java também se enquadram nessa categoria - elas exigem que as variáveis ​​locais capturadas sejam final, o que também significa que não há necessidade de compartilhar o estado.

Alguns idiomas permitem que você escolha entre capturar o valor de uma variável ou sua localização. Por exemplo, em C ++ 11, as variáveis ​​capturadas são declaradas com [&], o que significa capturadas por referência, ou com [=], que significa capturadas por valor.

Ainda outro subconjunto, linguagens funcionais preguiçosas , como Haskell , vinculam variáveis ​​a resultados de cálculos futuros em vez de valores. Considere este exemplo em Haskell:

-- Haskell
foo :: Fractional a => a -> a -> (a -> a)
foo x y = (\z -> z + r)
          where r = x / y

f :: Fractional a => a -> a
f = foo 1 0

main = print (f 123)

O vínculo de rcapturado pelo encerramento definido na função fooé para o cálculo (x / y)- que, neste caso, resulta na divisão por zero. No entanto, como é o cálculo que é capturado, e não o valor, o erro apenas se manifesta quando o encerramento é invocado e, na verdade, tenta usar a vinculação capturada.

Encerramento saindo

Ainda mais diferenças se manifestam no comportamento de outras construções de escopo léxico, tais como return, breake continuedeclarações. Essas construções podem, em geral, ser consideradas em termos de invocação de uma continuação de escape estabelecida por uma instrução de controle envolvente (no caso de breake continue, tal interpretação requer que as construções de loop sejam consideradas em termos de chamadas de função recursivas). Em algumas linguagens, como ECMAScript, returnrefere-se à continuação estabelecida pelo encerramento lexicamente mais interno em relação à instrução - portanto, a returndentro de um encerramento transfere o controle para o código que o chamou. No entanto, em Smalltalk , o operador superficialmente semelhante ^invoca a continuação de escape estabelecida para a invocação do método, ignorando as continuações de escape de quaisquer fechamentos aninhados intervenientes. A continuação de escape de um encerramento específico só pode ser invocada em Smalltalk implicitamente ao atingir o final do código do encerramento. Os exemplos a seguir em ECMAScript e Smalltalk destacam a diferença:

"Smalltalk"
foo
  | xs |
  xs := #(1 2 3 4).
  xs do: [:x | ^x].
  ^0
bar
  Transcript show: (self foo printString) "prints 1"
// ECMAScript
function foo() {
  var xs = [1, 2, 3, 4];
  xs.forEach(function (x) { return x; });
  return 0;
}
alert(foo()); // prints 0

Os trechos de código acima se comportarão de maneira diferente porque o ^operador Smalltalk e o returnoperador JavaScript não são análogos. No exemplo ECMAScript, return xdeixará o fechamento interno para iniciar uma nova iteração do forEachloop, enquanto no exemplo Smalltalk, ^xabortará o loop e retornará do método foo.

Common Lisp fornece uma construção que pode expressar qualquer uma das ações acima: Lisp (return-from foo x)se comporta como Smalltalk ^x , enquanto Lisp (return-from nil x)se comporta como JavaScript return x . Conseqüentemente, Smalltalk possibilita que uma continuação de escape capturada sobreviva até o ponto em que pode ser chamada com êxito. Considerar:

"Smalltalk"
foo
    ^[ :x | ^x ]
bar
    | f |
    f := self foo.
    f value: 123 "error!"

Quando o fechamento retornado pelo método fooé invocado, ele tenta retornar um valor da invocação fooque criou o fechamento. Como essa chamada já foi retornada e o modelo de invocação do método Smalltalk não segue a disciplina da pilha espaguete para facilitar retornos múltiplos, esta operação resulta em um erro.

Algumas linguagens, como Ruby , permitem que o programador escolha a forma como returné capturada. Um exemplo em Ruby:

# Ruby

# Closure using a Proc
def foo
  f = Proc.new { return "return from foo from inside proc" }
  f.call # control leaves foo here
  return "return from foo"
end

# Closure using a lambda
def bar
  f = lambda { return "return from lambda" }
  f.call # control does not leave bar here
  return "return from bar"
end

puts foo # prints "return from foo from inside proc"
puts bar # prints "return from bar"

Ambos Proc.newe lambdaneste exemplo são maneiras de criar um encerramento, mas a semântica dos encerramentos assim criados é diferente em relação à returninstrução.

Em Scheme , a definição e o escopo da returninstrução de controle são explícitos (e apenas arbitrariamente nomeados como 'return' por causa do exemplo). A seguir está uma tradução direta da amostra Ruby.

; Scheme
(define call/cc call-with-current-continuation)

(define (foo)
  (call/cc
   (lambda (return)
     (define (f) (return "return from foo from inside proc"))
     (f) ; control leaves foo here
     (return "return from foo"))))

(define (bar)
  (call/cc
   (lambda (return)
     (define (f) (call/cc (lambda (return) (return "return from lambda"))))
     (f) ; control does not leave bar here
     (return "return from bar"))))

(display (foo)) ; prints "return from foo from inside proc"
(newline)
(display (bar)) ; prints "return from bar"

Construções semelhantes a fechamento

Algumas linguagens possuem recursos que simulam o comportamento de fechamentos. Em linguagens como Java, C ++, Objective-C, C #, VB.NET e D, esses recursos são o resultado do paradigma orientado a objetos da linguagem.

Chamadas de retorno (C)

Algumas bibliotecas C oferecem suporte a retornos de chamada . Isso às vezes é implementado fornecendo dois valores ao registrar o retorno de chamada com a biblioteca: um ponteiro de função e um void*ponteiro separado para dados arbitrários de escolha do usuário. Quando a biblioteca executa a função de retorno de chamada, ela passa o ponteiro de dados. Isso permite que o retorno de chamada mantenha o estado e consulte as informações capturadas no momento em que foi registrado na biblioteca. O idioma é semelhante a encerramentos em funcionalidade, mas não em sintaxe. O void*ponteiro não é seguro para o tipo, portanto, este idioma C difere dos encerramentos com segurança para o tipo em C #, Haskell ou ML.

Callbacks são amplamente usados ​​em kits de ferramentas GUI Widget para implementar programação orientada a eventos , associando funções gerais de widgets gráficos (menus, botões, caixas de seleção, controles deslizantes, botões giratórios, etc.) com funções específicas do aplicativo que implementam o comportamento específico desejado para o aplicativo.

Função aninhada e ponteiro de função (C)

Com uma extensão gcc, uma função aninhada pode ser usada e um ponteiro de função pode emular fechamentos, desde que a função não saia do escopo que o contém. O exemplo a seguir é inválido porque adderé uma definição de nível superior (dependendo da versão do compilador, pode produzir um resultado correto se compilado sem otimização, ou seja, em -O0):

#include <stdio.h>

typedef int (*fn_int_to_int)(int); // type of function int->int

fn_int_to_int adder(int number) {
  int add (int value) { return value + number; }
  return &add; // & operator is optional here because the name of a function in C is a pointer pointing on itself
}

int main(void) {
  fn_int_to_int add10 = adder(10);
  printf("%d\n", add10(1));
  return 0;
}

Mas mover adder(e, opcionalmente, o typedef) maintorna-o válido:

#include <stdio.h>

int main(void) {
  typedef int (*fn_int_to_int)(int); // type of function int->int
  
  fn_int_to_int adder(int number) {
    int add (int value) { return value + number; }
    return add;
  }
  
  fn_int_to_int add10 = adder(10);
  printf("%d\n", add10(1));
  return 0;
}

Se executado, agora é impresso 11como esperado.

Classes locais e funções lambda (Java)

Java permite que classes sejam definidas dentro de métodos . Essas são chamadas de classes locais . Quando essas classes não são nomeadas, elas são conhecidas como classes anônimas (ou classes internas anônimas ). Uma classe local (nomeada ou anônima) pode referir-se a nomes em classes envolventes lexicamente ou variáveis ​​somente leitura (marcadas como final) no método envolvente lexicamente.

class CalculationWindow extends JFrame {
    private volatile int result;
    // ...
    public void calculateInSeparateThread(final URI uri) {
        // The expression "new Runnable() { ... }" is an anonymous class implementing the 'Runnable' interface.
        new Thread(
            new Runnable() {
                void run() {
                    // It can read final local variables:
                    calculate(uri);
                    // It can access private fields of the enclosing class:
                    result = result + 10;
                }
            }
        ).start();
    }
}

A captura de finalvariáveis ​​permite capturar variáveis ​​por valor. Mesmo se a variável que você deseja capturar for não- final, você sempre pode copiá-la para uma finalvariável temporária antes da classe.

A captura de variáveis ​​por referência pode ser emulada usando uma finalreferência a um contêiner mutável, por exemplo, uma matriz de elemento único. A classe local não poderá alterar o valor da referência do contêiner em si, mas poderá alterar o conteúdo do contêiner.

Com o advento das expressões lambda do Java 8, o encerramento faz com que o código acima seja executado como:

class CalculationWindow extends JFrame {
    private volatile int result;
    // ...
    public void calculateInSeparateThread(final URI uri) {
        // The code () -> { /* code */ } is a closure.
        new Thread(() -> {
            calculate(uri);
            result = result + 10;
        }).start();
    }
}

As classes locais são um dos tipos de classes internas declaradas no corpo de um método. Java também oferece suporte a classes internas que são declaradas como membros não estáticos de uma classe envolvente. Normalmente são chamados apenas de "classes internas". Eles são definidos no corpo da classe envolvente e têm acesso total às variáveis ​​de instância da classe envolvente. Devido à sua vinculação a essas variáveis ​​de instância, uma classe interna só pode ser instanciada com uma vinculação explícita a uma instância da classe envolvente usando uma sintaxe especial.

public class EnclosingClass {
    /* Define the inner class */
    public class InnerClass {
        public int incrementAndReturnCounter() {
            return counter++;
        }
    }

    private int counter;
    {
        counter = 0;
    }

    public int getCounter() {
        return counter;
    }

    public static void main(String[] args) {
        EnclosingClass enclosingClassInstance = new EnclosingClass();
        /* Instantiate the inner class, with binding to the instance */
        EnclosingClass.InnerClass innerClassInstance =
            enclosingClassInstance.new InnerClass();

        for (int i = enclosingClassInstance.getCounter();
             (i = innerClassInstance.incrementAndReturnCounter()) < 10;
             /* increment step omitted */) {
            System.out.println(i);
        }
    }
}

Na execução, isso imprimirá os inteiros de 0 a 9. Cuidado para não confundir esse tipo de classe com a classe aninhada, que é declarada da mesma forma com o uso acompanhado do modificador "estático"; aqueles não têm o efeito desejado, mas são apenas classes sem ligação especial definida em uma classe envolvente.

A partir do Java 8 , o Java oferece suporte a funções como objetos de primeira classe. Expressões lambda dessa forma são consideradas do tipo, Function<T,U>sendo T o domínio e U o tipo de imagem. A expressão pode ser chamada com seu .apply(T t)método, mas não com uma chamada de método padrão.

public static void main(String[] args) {
    Function<String, Integer> length = s -> s.length();

    System.out.println( length.apply("Hello, world!") ); // Will print 13.
}

Blocos (C, C ++, Objective-C 2.0)

A Apple introduziu os blocos , uma forma de fechamento, como uma extensão não padrão em C , C ++ , Objective-C 2.0 e no Mac OS X 10.6 "Snow Leopard" e iOS 4.0 . A Apple disponibilizou sua implementação para os compiladores GCC e clang.

Ponteiros para bloquear e bloquear literais são marcados com ^. Variáveis ​​locais normais são capturadas por valor quando o bloco é criado e são somente leitura dentro do bloco. As variáveis ​​a serem capturadas por referência são marcadas com __block. Os blocos que precisam persistir fora do escopo em que foram criados podem precisar ser copiados.

typedef int (^IntBlock)();

IntBlock downCounter(int start) {
	 __block int i = start;
	 return [[ ^int() {
		 return i--;
	 } copy] autorelease];
}

IntBlock f = downCounter(5);
NSLog(@"%d", f());
NSLog(@"%d", f());
NSLog(@"%d", f());

Delegados (C #, VB.NET, D)

Métodos anônimos C # e expressões lambda oferecem suporte ao fechamento:

var data = new[] {1, 2, 3, 4};
var multiplier = 2;
var result = data.Select(x => x * multiplier);

Visual Basic .NET , que tem muitos recursos de linguagem semelhantes aos do C #, também oferece suporte a expressões lambda com encerramentos:

Dim data = {1, 2, 3, 4}
Dim multiplier = 2
Dim result = data.Select(Function(x) x * multiplier)

Em D , os encerramentos são implementados por delegados, um ponteiro de função emparelhado com um ponteiro de contexto (por exemplo, uma instância de classe ou um quadro de pilha no heap no caso de encerramentos).

auto test1() {
    int a = 7;
    return delegate() { return a + 3; }; // anonymous delegate construction
}

auto test2() {
    int a = 20;
    int foo() { return a + 5; } // inner function
    return &foo;  // other way to construct delegate
}

void bar() {
    auto dg = test1();
    dg();    // =10   // ok, test1.a is in a closure and still exists

    dg = test2();
    dg();    // =25   // ok, test2.a is in a closure and still exists
}

D versão 1, tem suporte de fechamento limitado. Por exemplo, o código acima não funcionará corretamente, porque a variável a está na pilha e, após retornar de test (), ela não é mais válida para usá-la (provavelmente chamando foo via dg (), retornará um ' random 'inteiro). Isso pode ser resolvido alocando explicitamente a variável 'a' no heap ou usando structs ou classe para armazenar todas as variáveis ​​fechadas necessárias e construir um delegado a partir de um método que implementa o mesmo código. Os fechamentos podem ser passados ​​para outras funções, desde que sejam usados ​​apenas enquanto os valores referenciados ainda forem válidos (por exemplo, chamar outra função com um fechamento como um parâmetro de retorno de chamada) e são úteis para escrever código de processamento de dados genérico, portanto, esta limitação , na prática, muitas vezes não é um problema.

Essa limitação foi corrigida na versão D 2 - a variável 'a' será alocada automaticamente no heap porque é usada na função interna e um delegado dessa função pode escapar do escopo atual (por meio da atribuição a dg ou retorno). Quaisquer outras variáveis ​​locais (ou argumentos) que não são referenciados por delegados ou que são referenciados apenas por delegados que não escapam do escopo atual, permanecem na pilha, que é mais simples e mais rápida do que a alocação de heap. O mesmo é verdadeiro para os métodos de classe interna que fazem referência às variáveis ​​de uma função.

Objetos de função (C ++)

C ++ permite definir objetos de função por sobrecarga operator(). Esses objetos se comportam como funções em uma linguagem de programação funcional. Eles podem ser criados em tempo de execução e podem conter estados, mas não capturam variáveis ​​locais implicitamente como os fechamentos fazem. A partir da revisão de 2011 , a linguagem C ++ também oferece suporte a encerramentos, que são um tipo de objeto de função construído automaticamente a partir de uma construção de linguagem especial chamada expressão lambda . Um encerramento C ++ pode capturar seu contexto armazenando cópias das variáveis ​​acessadas como membros do objeto de encerramento ou por referência. No último caso, se o objeto de fechamento escapar do escopo de um objeto referenciado, invocar seu operator()comportamento indefinido de causas, uma vez que os fechamentos em C ++ não estendem o tempo de vida de seu contexto.

void foo(string myname) {
    int y;
    vector<string> n;
    // ...
    auto i = std::find_if(n.begin(), n.end(),
               // this is the lambda expression:
               [&](const string& s) { return s != myname && s.size() > y; }
             );
    // 'i' is now either 'n.end()' or points to the first string in 'n'
    // which is not equal to 'myname' and whose length is greater than 'y'
}

Agentes inline (Eiffel)

Eiffel inclui agentes em linha que definem fechamentos. Um agente embutido é um objeto que representa uma rotina, definido fornecendo o código da rotina embutido. Por exemplo, em

ok_button.click_event.subscribe (
	agent (x, y: INTEGER) do
		map.country_at_coordinates (x, y).display
	end
)

o argumento para subscribeé um agente, representando um procedimento com dois argumentos; o procedimento encontra o país nas coordenadas correspondentes e o exibe. Todo o agente é "inscrito" no tipo de evento click_eventpara um determinado botão, de modo que sempre que uma instância do tipo de evento ocorrer naquele botão - porque um usuário clicou no botão - o procedimento será executado com as coordenadas do mouse sendo passadas como argumentos para xe y.

A principal limitação dos agentes Eiffel, que os distingue dos encerramentos em outras linguagens, é que eles não podem fazer referência a variáveis ​​locais do escopo encerrado. Essa decisão de design ajuda a evitar ambigüidade ao falar sobre o valor de uma variável local em um fechamento - deve ser o último valor da variável ou o valor capturado quando o agente é criado? Apenas Current(uma referência ao objeto atual, análogo ao thisem Java), seus recursos e argumentos do próprio agente podem ser acessados ​​de dentro do corpo do agente. Os valores das variáveis ​​locais externas podem ser passados ​​fornecendo operandos fechados adicionais ao agente.

C ++ Builder __closure palavra reservada

O Embarcadero C ++ Builder fornece a palavra reserva __closure para fornecer um ponteiro para um método com uma sintaxe semelhante a um ponteiro de função.

No C padrão, você pode escrever um typedef para um ponteiro para um tipo de função usando a seguinte sintaxe:

typedef void (*TMyFunctionPointer)( void );

De maneira semelhante, você pode declarar um typedef para um ponteiro para um método usando a seguinte sintaxe:

typedef void (__closure *TMyMethodPointer)();

Veja também

Notas

Referências

links externos