Usar do block vs braces {}

112

Nuevo en ruby, ponte tus guantes de novato.

¿Hay alguna diferencia (oscura o práctica) entre los siguientes dos fragmentos?

my_array = [:uno, :dos, :tres]
my_array.each { |item| 
    puts item
}

my_array = [:uno, :dos, :tres]
my_array.each do |item| 
    puts item
end

Me doy cuenta de que la sintaxis de llaves te permitiría colocar el bloque en una línea

my_array.each { |item| puts item }

pero fuera de eso, ¿existen razones de peso para usar una sintaxis sobre la otra?

Alan Storm
fuente
5
Gran pregunta. También me interesa lo que prefieren los rubíes experimentados.
Jonathan Sterling
3
Duplicado: stackoverflow.com/questions/533008
Mike Woodhouse
2
posible duplicado de stackoverflow.com/questions/533008/…
John Topley
1
También puede escribir una línea del bloque do aunque en realidad solo es útil cuando se hace algo como eval ("my_array.each do | item |; puts item; end") pero funciona en irb o pry sin eval y comillas. puede encontrarse con una situación en la que se prefiera. Sin embargo, no me preguntes cuándo. Ese es otro tema a investigar.
Douglas G. Allen

Respuestas:

101

El libro de cocina de Ruby dice que la sintaxis de corchetes tiene un orden de precedencia más alto quedo..end

Tenga en cuenta que la sintaxis de corchetes tiene mayor precedencia que la sintaxis do..end. Considere los siguientes dos fragmentos de código:

1.upto 3 do |x|
  puts x
end

1.upto 3 { |x| puts x }
# SyntaxError: compile error

El segundo ejemplo solo funciona cuando se utilizan paréntesis, 1.upto(3) { |x| puts x }


fuente
8
Ah, lo tengo. Entonces, debido al orden de precedencia, cuando usa do, está pasando el bloque como un parámetro adicional, pero cuando usa los corchetes, está pasando el bloque como el primer parámetro de los resultados de la (s) invocación (es) del método a la izquierda.
Alan Storm
2
A menudo prefiero respuestas cortas, pero la respuesta de bkdir es mucho más clara.
yakout
71

Esta es una pregunta un poco vieja, pero me gustaría intentar explicar un poco más sobre {}ydo .. end

como se dijo antes

la sintaxis de corchetes tiene un orden de precedencia más alto que do..end

pero cómo este hace la diferencia:

method1 method2 do
  puts "hi"
end

en este caso, se llamará al método1 con el bloque de do..endy el método2 se pasará al método1 como argumento. que es equivalente amethod1(method2){ puts "hi" }

pero si dices

method1 method2{
  puts "hi"
}

luego se llamará al método2 con el bloque y luego el valor devuelto se pasará al método1 como argumento. Que es equivalente amethod1(method2 do puts "hi" end)

def method1(var)
    puts "inside method1"
    puts "method1 arg = #{var}"
    if block_given?
        puts "Block passed to method1"
        yield "method1 block is running"
    else
        puts "No block passed to method1"
    end
end

def method2
    puts"inside method2"
    if block_given?
        puts "Block passed to method2"
        return yield("method2 block is running")
    else
        puts "no block passed to method2"
        return "method2 returned without block"
    end
end

#### test ####

method1 method2 do 
    |x| puts x
end

method1 method2{ 
    |x| puts x
}

#### salida ####

#inside method2
#no block passed to method2
#inside method1
#method1 arg = method2 returned without block
#Block passed to method1
#method1 block is running

#inside method2
#Block passed to method2
#method2 block is running
#inside method1
#method1 arg = 
#No block passed to method1
bkdir
fuente
39

Generalmente, la convención es usar {}cuando está haciendo una operación pequeña, por ejemplo, una llamada a un método o una comparación, etc., por lo que esto tiene mucho sentido:

some_collection.each { |element| puts element }

Pero si tiene una lógica ligeramente compleja que va a varias líneas, use do .. endcomo:

1.upto(10) do |x|
  add_some_num = x + rand(10)
  puts '*' * add_some_num
end

Básicamente, se reduce a, si su lógica de bloque va a varias líneas y no se puede ajustar en la misma línea, entonces use do .. endy si su lógica de bloque es simple y solo una línea de código simple / única, entonces use {}.

nas
fuente
6
Estoy de acuerdo con usar do / end para bloques de varias líneas, pero usaré llaves si estoy encadenando métodos adicionales al final del bloque. Estilísticamente me gusta {...}. Method (). Method () sobre do ... end.method (). Method, pero ese podría ser solo yo.
The Tin Man
1
De acuerdo, aunque prefiero asignar el resultado de un método con bloque a una variable significativa y luego llamar a otro método, result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodya que eso lo hace un poco más comprensible. Pero generalmente evitaría un choque de trenes.
nas
Me pregunto por qué este estilo es tan popular. ¿Hay alguna razón para ello además de "Siempre lo hicimos de esta manera"?
iGEL
9

Hay dos estilos comunes para elegir do endvs. { }para bloques en Ruby:

El primer y muy común estilo fue popularizado por Ruby on Rails, y se basa en una regla simple de una o varias líneas:

  • Use tirantes { }para bloques de una sola línea
  • Utilizar do endpara bloques de varias líneas

Esto tiene sentido porque do / end se lee mal en una sola línea, pero para bloques de varias líneas, dejar un cierre }colgando en su propia línea es inconsistente con todo lo demás que se usa enden ruby, como las definiciones de módulo, clase y método ( defetc. .) y las estructuras de control ( if, while, case, etc.)

El segundo estilo, menos visto, se conoce como semántico, o " Weirich Braces ", propuesto por el difunto gran rubyista Jim Weirich:

  • Usar do endpara bloques de procedimiento
  • Use tirantes { }para bloques funcionales

Esto significa que cuando el bloque se evalúa por su valor de retorno , debe ser encadenable y las {}llaves tienen más sentido para el encadenamiento de métodos.

Por otro lado, cuando el bloque se evalúa por sus efectos secundarios , entonces el valor de retorno no tiene importancia y el bloque simplemente está "haciendo" algo, por lo que no tiene sentido estar encadenado.

Esta distinción en la sintaxis transmite un significado visual sobre la evaluación del bloque y si debe o no preocuparse por su valor de retorno.

Por ejemplo, aquí el valor de retorno del bloque se aplica a cada elemento:

items.map { |i| i.upcase }

Sin embargo, aquí no se usa el valor de retorno del bloque. Se está operando de procedimiento, y haciendo un efecto secundario con ella:

items.each do |item|
  puts item
end

Otro beneficio del estilo semántico es que no es necesario cambiar las llaves para hacer / terminar solo porque se agregó una línea al bloque.

Como observación, casualmente los bloques funcionales son frecuentemente de una sola línea, y los bloques de procedimiento (por ejemplo, config) son de múltiples líneas. Entonces, seguir el estilo Weirich termina luciendo casi igual que el estilo Rails.

Andrew Vit
fuente
1

Usé el estilo Weirich durante años, pero me alejé de esto para usar siempre aparatos ortopédicos . No recuerdo haber usado nunca la información del estilo de bloque, y la definición es un poco vaga. Por ejemplo:

date = Timecop.freeze(1.year.ago) { format_date(Time.now) }
customer = Timecop.freeze(1.year.ago) { create(:customer) }

¿Son procudual o funcional?

Y la cosa del recuento de líneas es inútil en mi opinión. Lo sé, si hay 1 o más líneas, y ¿por qué exactamente debería cambiar el estilo solo porque agregué o eliminé líneas?

iGEL
fuente