La mejor explicación de los bloques de Ruby?

¿Cuál es la mejor explicación para los bloques de Ruby que puedes compartir?

¿Código de uso y escritura que puede tomar un bloque?

Ofrezco mi propia explicación de esta respuesta , ligeramente modificada:

Los “Bloques” en Ruby no son los mismos que los términos de progtwigción general “bloque de código” o “bloque de código”.

Imagine por un momento que el siguiente código de Ruby (inválido) realmente funcionó:

def add10( n ) puts "#{n} + 10 = #{n+10}" end def do_something_with_digits( method ) 1.upto(9) do |i| method(i) end end do_something_with_digits( add10 ) #=> "1 + 10 = 11" #=> "2 + 10 = 12" ... #=> "9 + 10 = 19" 

Si bien ese código no es válido, su bash de pasar un código a un método y hacer que ese método ejecute el código es posible en Ruby de varias maneras. Una de esas formas es “Bloques”.

Un bloque en Ruby es muy, muy parecido a un método: puede tomar algunos argumentos y ejecutar código para esos. Siempre que vea foo{ |x,y,z| ... } foo{ |x,y,z| ... } o foo do |x,y,z| ... end foo do |x,y,z| ... end , esos son bloques que toman tres parámetros y ejecutan ... en ellos. (Incluso puede ver que el método upto anterior se está pasando un bloque).

Debido a que los Bloques son una parte especial de la syntax de Ruby, todos los métodos pueden pasar un bloque. Si el método usa o no el bloque depende del método. Por ejemplo:

 def say_hi( name ) puts "Hi, #{name}!" end say_hi("Mom") do puts "YOU SUCK!" end #=> Hi, Mom! 

El método anterior se pasa un bloque que está listo para emitir un insulto, pero dado que el método nunca llama al bloque, solo se imprime el bonito mensaje. Así es como llamamos al bloque desde un método:

 def say_hi( name ) puts "Hi, #{name}!" if block_given? yield( name ) end end say_hi("Mridang") do |str| puts "Your name has #{str.length} letters." end #=> Hi, Mridang! #=> Your name has 7 letters. 

Usamos block_given? para ver si se pasó o no un locking o no. En este caso, pasamos una discusión al bloque; depende de su método decidir qué pasar al bloque. Por ejemplo:

 def say_hi( name ) puts "Hi, #{name}!" yield( name, name.reverse ) if block_given? end say_hi("Mridang"){ |str1, str2| puts "Is your name #{str1} or #{str2}?" } #=> Hi, Mridang! #=> Is your name Mridang or gnadirM? 

Es solo una convención (y una buena, y una que desea apoyar) para que algunas clases pasen la instancia recién creada al bloque.

Esta no es una respuesta exhaustiva, ya que no cubre la captura de bloques como argumentos, cómo manejan la aridad, la eliminación de errores en los parámetros del bloque, etc., pero tiene la intención de servir como una introducción de Blocks-Are-Lambdas.

Los bloques de Ruby son una forma de crear objetos Proc que representan código que puede ser usado por otro código. Los objetos Proc son instrucciones entre llaves {} (o do...end frases para bloques de líneas múltiples, que tienen menor prioridad que las llaves) que opcionalmente pueden tomar argumentos y devolver valores (por ejemplo, {|x,y| x+y} ) . Los procs son objetos de primera clase y pueden construirse explícitamente o alcanzarse implícitamente como pseudoargumentos de método:

  1. Construcción como un objeto Proc (o usando la palabra clave lambda ):

     add1 = Proc.new {|x| x+1} # Returns its argument plus one. add1.call(1) # => 2 
  2. Pasó como un método de pseudo-argumento, ya sea explícitamente utilizando el operador de azúcar de syntax especial & último argumento o implícitamente usando un block_given? / par de yield :

     def twice_do(&proc) # "proc" is the block given to a call of this method. 2.times { proc.call() } if proc end twice_do { puts "OK" } # Prints "OK" twice on separate lines. def thrice_do() # if a block is given it can be called with "yield". 3.times { yield } if block_given? end thrice_do { puts "OK" } # Prints "OK" thrice on separate lines. 

La segunda forma se usa típicamente para patrones de Visitantes ; los datos se pueden pasar a los argumentos de bloque especiales como argumentos a los métodos de call o yield .

De la guía de Why’s (conmovedora) a ruby :

Cualquier código rodeado de llaves es un bloque.

2.times { print "Yes, I've used chunky bacon in my examples, but never again!" } 2.times { print "Yes, I've used chunky bacon in my examples, but never again!" } es un ejemplo.

Con bloques, puede agrupar un conjunto de instrucciones para que se puedan pasar a su progtwig. Las llaves tienen la apariencia de pinzas de cangrejo que han arrebatado el código y lo mantienen unido. Cuando vea estas dos pinzas, recuerde que el código interno se ha presionado en una sola unidad. Es como una de esas pequeñas cajas de Hello Kitty que venden en el centro comercial que está repleta de diminutos lápices y papel microscópico, todos metidos en una caja transparente shiny que puede ocultarse en la palma de su mano para operaciones estacionarias encubiertas. Excepto que los bloques no requieren tanto entrecerrar los ojos. Las llaves también se pueden cambiar por las palabras do y end, lo cual es bueno si tu bloque es más largo que una línea.

 loop do print "Much better." print "Ah. More space!" print "My back was killin' me in those crab pincers." end 

Los argumentos de bloque son un conjunto de variables rodeado por caracteres de canal y separados por comas.

 |x|, |x,y|, and |up, down, all_around| are examples. 

Los argumentos de bloque se usan al comienzo de un bloque.

 { |x,y| x + y } 

En el ejemplo anterior, | x, y | son los argumentos. Después de los argumentos, tenemos un poco de código. La expresión x + y agrega los dos argumentos juntos. Me gusta pensar que los personajes de las tuberías representan un túnel. Dan la apariencia de un canal inclinado en el que las variables se deslizan hacia abajo. (Una x baja águila extendida, mientras que cruza prolijamente sus piernas.) Esta rampa actúa como un pasillo entre los bloques y el mundo que los rodea. Las variables se pasan a través de este conducto (o túnel) al bloque.

El libro ” Programming Ruby ” tiene una gran explicación de los bloques y su uso .

En 1.9+, la lista de parámetros pasada a un bloque se volvió más sofisticada, permitiendo definir las variables locales:

 do |a,b;c,d| some_stuff end 

;c,d declaran dos nuevas variables locales dentro del bloque, que no reciben valores de la statement de yield la rutina llamada. Ruby 1.9+ garantiza que, si las variables existieron fuera del bloque, no serán pisoteadas por las mismas variables dentro del bloque. Este es un nuevo comportamiento; 1.8 pisaría ellos.

 def blah yield 1,2,3,4 end c = 'foo' d = 'bar' blah { |a, *b; c,d| c = 'hello' d = 'world' puts "a: #{a}", "b: #{b.join(',')}", "c: #{c}", "d: #{d}" } puts c, d # >> a: 1 # >> b: 2,3,4 # >> c: hello # >> d: world # >> foo # >> bar 

También está el operador “splat” * , que funciona en la lista de parámetros:

 do |a,*b| some_stuff end 

Asignaría el primero de varios valores a “a”, y todo el rest se capturaría en “b”, que se trataría como una matriz. El * podría estar en la variable a:

 do |*a,b| some_stuff end 

capturaría todas las variables aprobadas, excepto la última, que se pasaría a b . Y, de manera similar a los dos anteriores:

 do |a,*b,c| some_stuff end 

asignaría el primer valor a a , el último valor a c y todos / cualquier valor intermedio a b .

Creo que es bastante poderoso y astuto.

Por ejemplo:

 def blah yield 1,2,3,4 end blah { |a, *b| puts "a: #{a}", "b: #{b.join(',')}" } # >> a: 1 # >> b: 2,3,4 blah { |*a, b| puts "a: #{a.join(',')}", "b: #{b}" } # >> a: 1,2,3 # >> b: 4 blah { |a, *b, c| puts "a: #{a}", "b: #{b.join(',')}", "c: #{c}" } # >> a: 1 # >> b: 2,3 # >> c: 4 

Para cualquiera que llegue a esta pregunta desde un fondo de C # (u otras lenguas realmente), esto podría ayudar:

Los bloques de Ruby son como expresiones lambda y métodos anónimos en C #. Son lo que C # llama a los delegates (y Ruby llama a los Procs), lo que quiere decir que son esencialmente funciones que se pueden pasar como valores. Tanto en Ruby como en C #, también pueden comportarse como cierres.

Ruby: { |x| x + 1 } { |x| x + 1 }

C #: x => x + 1

Ruby: { |name| puts "Hello there #{name}" } { |name| puts "Hello there #{name}" }

C #: name => { Console.WriteLine("Hello there {0}", name); } name => { Console.WriteLine("Hello there {0}", name); }

Tanto C # como Ruby ofrecen formas alternativas de escribir el ejemplo anterior.

Rubí:

 do |name| puts "Hello there #{name}" end 

DO#:

 delegate(string name) { Console.WriteLine("Hello there {0}", name); } 

Tanto en Ruby como en C #, se permiten múltiples instrucciones, en Ruby, la segunda syntax anterior es necesaria para esto.

Estos conceptos están disponibles en muchos otros idiomas que han sido influenciados por las ideas detrás de la progtwigción funcional.

Los bloques son una forma de agrupar código en Ruby. Hay dos formas de escribir bloques. Uno está usando la instrucción do..end y el otro está rodeando el código con llaves: {}. Los bloques se consideran objetos en el lenguaje de progtwigción Ruby y, de forma predeterminada, todas las funciones aceptan un argumento de bloque implícito.

Aquí hay dos ejemplos de bloques que hacen lo mismo:

 2.times {puts 'hi'}
 2. veces
   pone 'hola'
 fin

Los bloques pueden recibir listas de argumentos separados por comas dentro de barras verticales ||. Por ejemplo:

 [1,2] .map {| n |  n + 2} # [3, 4]

Los bloques (en ruby ​​1.9.2) pueden tener explícitamente variables locales:

 x = 'hola'
 2.times do |; x |
   x = 'mundo'
   pone x
 fin

 => mundo
 => mundo

Las variables locales se pueden combinar con parámetros:

 [1,2] .map {| n; x |  n + 2}

Todas las funciones pueden recibir un argumento de bloque predeterminado:

 def dos veces
   rendimiento
   rendimiento
 fin

 dos veces {pone 'hola'}
 => hola
 => hola

¿Cuál es la diferencia entre do … end y {} bloques? Por convención {} los bloques están en una sola línea y los bloques de … finalización abarcan múltiples líneas, ya que cada uno de ellos es más fácil de leer de esta manera. La principal diferencia tiene que ver con la precedencia:

 array = [1,2]

 puts array.map {| n |  n * 10} # puts (array.map {| n | n * 10})
 => 10
 => 20

 puts array.map do | n |  n * 10 fin # (puts array.map) do | n |  n * 10 final
 => 

Los bloques son literales livianos para procedimientos anónimos de primera clase con algunas limitaciones molestas. Funcionan de la misma manera en Ruby, ya que funcionan en casi todos los demás lenguajes de progtwigción, modulo de las limitaciones antes mencionadas, que son:

  • los bloques solo pueden aparecer en las listas de argumentos
  • a lo sumo un bloque puede aparecer en una lista de argumentos (y debe ser el último argumento)