¿Qué es un cierre?
Un cierre es un bloque de código funcional con variables que están vinculadas al entorno en el que se llama al cierre.
Un cierre tiene algunas propiedades importantes:
- se puede pasar como un objeto (pero eso no significa necesariamente que sea un objeto, como analizaremos a continuación),
- un cierre se puede definir en un ámbito y llamar a un ámbito completamente diferente,
- recuerda las variables dentro de su ámbito en el momento de la creación; cuando se llama, puede acceder a esas variables incluso si no están en ese ámbito actual, lo que significa que un cierre conserva el conocimiento de su entorno léxico en el momento en que se definió.
Bloques
Los bloques son una forma simple de cierre en Ruby. A diferencia de todo lo demás en Ruby, los bloques no son objetos.Los bloques
se construyen con do...end
o con {}
.
Ya debería estar familiarizado con los bloques al trabajar con iteradores y enumeradores.
Aquí hay dos ejemplos familiares de bloques:
El método enumerador .each
acepta un bloque como argumento y llama al bloque dado una vez para cada elemento de la colección.
array.each do |x| puts xend#orarray.each { |x| puts x }
Es posible que haya visto esto dentro de una prueba RSpec; al método before(:each)
se le da un bloque al que rinde para cada una de las pruebas dentro del mismo contexto.
before(:each) do @language = "Ruby"end
Procs
Procs son un tipo de cierre en Ruby que se comportan de manera muy similar a los bloques, pero con algunas diferencias clave:
- se puede asignar un proc a una variable local,
- se ejecuta una instancia de proc llamando al método
call
en él, y - se puede pasar más de un proc a un método.
Pero esencialmente, un proc es un bloque convertido en un objeto al ser asignado a una instancia de la clase Proc.
Por lo tanto, la creación de un proc se ve muy similar a la instanciación de una clase.
Aquí estamos asignando un bloque a una instancia de la clase Proc y asignándolo a una variable. Entonces estamos llamando al método .call
en él:
greeting = Proc.new { "Hello!" }greeting.call=> "Hello!"
Procs también puede aceptar argumentos que se pasan al método .call
:
greeting = Proc.new { |name| "Hello, #{name}!" }greeting.call("Amanda")=> "Hello, Amanda!"
Lambdas
Una lambda no es muy diferente de un proc en funcionalidad.
Aquí hay una lambda que se pasa al enumerador cada una. El &
antes de convertirlo en un bloque, que cada uno espera como argumento.
plus_one = lambda { |n| puts n + 1 }array = array.each(&plus_one)=> 2=> 3=> 4
Una lambda también se puede declarar de esta manera divertida:
plus_one = ->(n) { puts n + 1 }
Lambdas y procs se usan casi indistintamente, pero hay algunas diferencias entre los dos:
- lambdas espera una cierta cantidad de argumentos y los comprueba; procs solo devuelve
nil
para el argumento que falta, pero no arroja un error - regresan de manera diferente: lambdas regresa al método de llamada y procs regresa inmediatamente sin volver a la persona que llama—esencialmente, lambdas se comporta más como llamadas a métodos: puede pensar en ellas como funciones anónimas
Si esto le resulta confuso, en particular los procs y lambdas, ¡no se preocupe! Procs y lambdas son increíbles y te permiten escribir código interesante y potente. Sin embargo, día a día no trabajarás mucho con ellos. Bloques, sin embargo, que va a interactuar con todos los días. Concéntrate en conocerlos mejor.