O que um closure captura
O que já foi coberto
A aula "Proc e lambda: uma olhada breve", do curso Blocos Ruby (do..end), já cobriu bastante coisa. &block transforma um bloco recebido num objeto Proc, e Proc.new e proc constroem um diretamente. .call, .() e [] são três formas de rodá-lo. lambda e o literal stabby ->() { } constroem uma versão mais rígida. Aquela aula apontou uma diferença: um lambda levanta ArgumentError com o número errado de argumentos, enquanto um Proc comum deixa o argumento que falta virar nil. Nada disso se repete aqui. Este curso usa Proc e lambda do jeito que aquela aula os deixou, para chegar a algo que ela nunca nomeou: a palavra closure, e o que ela significa.
Um método retorna, e um valor não some
def make_greeter(name)
greeting = "Hello"
->(punct) { "#{greeting}, #{name}#{punct}" }
end
g = make_greeter("Ada")
g.call("!")
# "Hello, Ada!"
make_greeter roda, tem duas variáveis locais, greeting e name, monta um lambda e o devolve. Quando g.call("!") roda, make_greeter já terminou. A chamada acabou, e as variáveis locais de uma chamada de método encerrada normalmente deixam de existir. Mesmo assim, g.call("!") alcança greeting e name e produz "Hello, Ada!".
A definição
Um closure é um Proc ou lambda que captura as variáveis locais visíveis no momento em que é criado. Ele mantém acesso vivo a elas enquanto o próprio Proc ou lambda existir, esteja ou não rodando o método que definiu essas variáveis. Todo Proc e todo lambda em Ruby é um closure. É uma propriedade que os dois têm, e não um terceiro tipo de objeto.
Acesso vivo, não uma foto
"Acesso vivo" é uma afirmação que dá para checar, e quer dizer mais que "lembra". Mude uma variável capturada depois de criar o closure, antes de chamá-lo, e veja qual valor ele recebe:
def make_reporter
status = "pending"
reporter = -> { status }
status = "done"
reporter
end
r = make_reporter
r.call
# "done", não "pending"
status valia "pending" quando reporter foi criado. Se um closure copiasse as variáveis capturadas na criação, r.call devolveria "pending" para sempre. Ele devolve "done", então o lambda guarda uma ligação com a variável status, e não uma cópia congelada do valor que ela tinha quando o lambda foi montado.
Por que isso importa no resto do curso
Todas as aulas seguintes dependem desse fato. Um Proc ou lambda é mais que "um código para rodar depois": é código junto com acesso contínuo às variáveis locais que existiam em volta dele quando foi criado. A próxima aula se apoia nisso. O método dela devolve um pedaço de estado que nenhum outro código alcança, a não ser pelo closure que ele retorna.
Pontos-chave
- Um closure é um
Procoulambdaque captura as variáveis locais visíveis na criação e mantém acesso vivo a elas depois que o método que as definiu retorna. - Todo
Proce todolambdaem Ruby é um closure; é uma propriedade deles, e não um tipo separado de objeto. - Mudar uma variável capturada depois de criar o closure, antes de chamá-lo, muda o que o closure vê, então ele guarda uma ligação viva, e não uma foto.
- O resto do curso se apoia nesse acesso vivo, começando por um método que devolve estado alcançável só pelo closure.
Continue com a aula 2
Uma fábrica de contadores com estado próprio
Monta make_counter, cujo lambda reatribui o próprio count privado, e cada chamada gera estado independente.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.