Usando do block vs colchetes {}


112

Novo no Ruby, coloque suas luvas de novato.

Existe alguma diferença (obscura ou prática) entre os dois trechos a seguir?

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

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

Sei que a sintaxe da chave permite que você coloque o bloco em uma linha

my_array.each { |item| puts item }

mas fora disso, há alguma razão convincente para usar uma sintaxe em vez de outra?


5
Ótima pergunta. Também estou interessado no que os rubistas experientes preferem.
Jonathan Sterling




1
Você também pode escrever uma linha do bloco do, embora seja realmente útil apenas ao fazer algo como eval ("my_array.each do | item |; puts item; end"), mas funciona no irb ou erguer sem eval e aspas Você pode se deparar com uma situação em que preferir Não me pergunte quando. Esse é outro assunto a ser investigado.
Douglas G. Allen

Respostas:


100

O livro de receitas Ruby diz que a sintaxe dos colchetes tem ordem de precedência superiordo..end

Lembre-se de que a sintaxe dos colchetes tem uma precedência mais alta do que a sintaxe do..end. Considere os seguintes dois snippets de código:

1.upto 3 do |x|
  puts x
end

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

O segundo exemplo só funciona quando são usados ​​parênteses, 1.upto(3) { |x| puts x }


7
Ah, entendi. Então, por causa da ordem de precedência, quando você usa do, você está passando o bloco como um parâmetro adicional, mas quando você usa os colchetes, você está passando o bloco como o primeiro parâmetro dos resultados da (s) invocação (ões) do método para a esquerda.
Alan Storm

2
Muitas vezes prefiro respostas curtas, mas a resposta de bkdir é muito mais clara.
yakout de

70

Esta é uma pergunta um pouco antiga, mas gostaria de tentar explicar um pouco mais sobre {}edo .. end

como foi dito antes

a sintaxe dos colchetes tem ordem de precedência mais alta do que do..end

mas como este faz a diferença:

method1 method2 do
  puts "hi"
end

neste caso, o método1 será chamado com o bloco de do..ende o método2 será passado para o método1 como um argumento! que é equivalente amethod1(method2){ puts "hi" }

mas se você diz

method1 method2{
  puts "hi"
}

então o método2 será chamado com o bloco e o valor retornado será passado para o método1 como um argumento. Que é 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
}

#### resultado ####

#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

39

Geralmente, a convenção é para usar {}quando você está fazendo uma pequena operação, por exemplo, uma chamada de método ou uma comparação, etc., então isso faz todo o sentido:

some_collection.each { |element| puts element }

Mas se você tiver uma lógica ligeiramente complexa que vai para várias linhas, use do .. endcomo:

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

Basicamente, se sua lógica de bloco vai para várias linhas e não pode ser encaixada na mesma linha, use do .. ende se sua lógica de bloco for simples e apenas uma linha simples / única de código, use {}.


6
Eu concordo em usar do / end para blocos de várias linhas, mas vou usar colchetes se estou encadeando métodos adicionais ao final do bloco. Estilisticamente, gosto de {...}. Method (). Method () em vez de do ... end.method (). Method, mas posso ser apenas eu.
The Tin Man,

1
Concordo, embora eu prefira atribuir o resultado de um método com bloco a uma variável significativa e, em seguida, chamar outro método, result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodpois isso apenas o torna um pouco mais fácil de entender. Mas geralmente evito um desastre de trem.
nas

Eu me pergunto por que esse estilo é tão popular. Existe alguma razão para isso além de "Sempre fizemos assim"?
iGEL de

9

Há dois estilos comuns para a escolha do endvs. { }para blocos em Ruby:

O primeiro e muito comum estilo foi popularizado por Ruby on Rails e é baseado em uma regra simples de linha única vs. multilinha:

  • Use colchetes { }para blocos de linha única
  • Use do endpara blocos multilinhas

Isso faz sentido porque do / end lê mal em uma linha, mas para blocos de várias linhas, deixar um fechamento }pendurado em sua própria linha é inconsistente com tudo o mais que usa endem ruby, como módulo, definição de classe e método ( defetc .) e estruturas de controlo ( if, while, case, etc.)

O segundo estilo, visto com menos frequência, é conhecido como semântico, ou " Chaves Weirich ", proposto pelo falecido grande rubista Jim Weirich:

  • Use do endpara bloqueios procedurais
  • Use chaves { }para blocos funcionais

Isso significa que quando o bloco é avaliado por seu valor de retorno , ele deve ser encadeado e as {}chaves fazem mais sentido para o encadeamento de métodos.

Por outro lado, quando o bloco é avaliado quanto aos seus efeitos colaterais , o valor de retorno não tem importância, e o bloco está apenas "fazendo" alguma coisa, então não faz sentido ser encadeado.

Esta distinção na sintaxe transmite um significado visual sobre a avaliação do bloco e se você deve ou não se preocupar com seu valor de retorno.

Por exemplo, aqui o valor de retorno do bloco é aplicado a todos os itens:

items.map { |i| i.upcase }

No entanto, aqui não está usando o valor de retorno do bloco. Está operando de maneira processual e causando um efeito colateral com isso:

items.each do |item|
  puts item
end

Outro benefício do estilo semântico é que você não precisa alterar os colchetes para fazer / terminar apenas porque uma linha foi adicionada ao bloco.

Como observação, os blocos funcionais coincidentemente são frequentemente de uma linha e os blocos de procedimento (por exemplo, configuração) são multilinhas. Então, seguir o estilo Weirich acaba ficando quase igual ao estilo Rails.


1

Usei o estilo Weirich por anos, mas me afastei disso para sempre usar aparelho . Não me lembro de nunca ter usado o estilo de informação do bloco, e a definição é meio vaga. Por exemplo:

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

São procuduais ou funcionais?

E a contagem de linha é simplesmente inútil na minha opinião. Eu sei, se há uma ou mais linhas, e por que exatamente devo alterar o estilo só porque adicionei ou removi linhas?

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.