Utiliser do block vs accolades {}

112

Nouveau sur ruby, mettez vos gants pour débutants.

Y a-t-il une différence (obscure ou pratique) entre les deux extraits de code suivants?

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

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

Je réalise que la syntaxe d'accolade vous permettrait de placer le bloc sur une seule ligne

my_array.each { |item| puts item }

mais en dehors de cela, y a-t-il des raisons impérieuses d'utiliser une syntaxe plutôt qu'une autre?

Alan Storm
la source
5
Excellente question. Je suis également intéressé par ce que préfèrent les rubisistes expérimentés.
Jonathan Sterling
3
Dupliquer: stackoverflow.com/questions/533008
Mike Woodhouse
2
duplication possible de stackoverflow.com/questions/533008/…
John Topley
1
Vous pouvez également écrire une ligne unique du bloc do bien que ce ne soit vraiment utile que lorsque vous faites quelque chose comme eval ("my_array.each do | item |; met item; end") mais cela fonctionne en irb ou pry sans eval et guillemets Vous peut rencontrer une situation où cela est préféré. Mais ne me demandez pas quand. C'est un autre sujet à étudier.
Douglas

Réponses:

101

Le livre de recettes Ruby indique que la syntaxe des crochets a un ordre de priorité plus élevé quedo..end

Gardez à l'esprit que la syntaxe entre crochets a une priorité plus élevée que la syntaxe do..end. Considérez les deux extraits de code suivants:

1.upto 3 do |x|
  puts x
end

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

Le deuxième exemple ne fonctionne que lorsque les parenthèses sont utilisées, 1.upto(3) { |x| puts x }

TU
la source
8
Ah, compris. Donc, en raison de l'ordre de priorité, lorsque vous utilisez do, vous passez le bloc en tant que paramètre supplémentaire, mais lorsque vous utilisez les crochets, vous passez le bloc comme premier paramètre des résultats de l'invocation de méthode à la gauche.
Alan Storm
2
Je préfère souvent des réponses courtes mais la réponse de bkdir est beaucoup plus claire.
yakout
71

C'est une question un peu ancienne mais j'aimerais essayer d'expliquer un peu plus sur {}etdo .. end

comme il est dit avant

la syntaxe entre crochets a un ordre de priorité plus élevé que do..end

mais comment celui-ci fait la différence:

method1 method2 do
  puts "hi"
end

dans ce cas, method1 sera appelé avec le bloc de do..endet method2 sera passé à method1 comme argument! ce qui équivaut àmethod1(method2){ puts "hi" }

mais si tu dis

method1 method2{
  puts "hi"
}

puis method2 sera appelée avec le bloc puis la valeur retournée sera transmise à method1 comme argument. Ce qui équivaut àmethod1(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
}

#### production ####

#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
la source
39

Généralement, la convention est à utiliser {}lorsque vous effectuez une petite opération, par exemple, un appel de méthode ou une comparaison, etc.

some_collection.each { |element| puts element }

Mais si vous avez une logique légèrement complexe qui va sur plusieurs lignes, utilisez do .. endcomme:

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

Fondamentalement, cela se résume à, si votre logique de bloc va sur plusieurs lignes et ne peut pas être installée sur la même ligne, utilisez do .. endet si votre logique de bloc est simple et juste une simple / seule ligne de code, utilisez {}.

nas
la source
6
Je suis d'accord avec l'utilisation de do / end pour les blocs multilignes, mais j'irai avec des accolades si je chaîne des méthodes supplémentaires à la fin du bloc. Stylistiquement, j'aime {...}. Method (). Method () plutôt que do ... end.method (). Method, mais c'est peut-être moi.
the Tin Man
1
D'accord, même si je préfère attribuer le résultat d'une méthode avec bloc à une variable significative, puis appeler une autre méthode dessus, result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodcar cela le rend un peu plus facilement compréhensible. Mais en général, j'éviterais un accident de train.
nas
Je me demande pourquoi ce style est si populaire. Y a-t-il une raison autre que "Nous l'avons toujours fait de cette façon"?
iGEL du
9

Il existe deux styles courants pour choisir do endou { }pour les blocs dans Ruby:

Le premier style très courant a été popularisé par Ruby on Rails, et est basé sur une règle simple de simple vs multi-ligne:

  • Utiliser des accolades { }pour les blocs sur une seule ligne
  • Utiliser do endpour les blocs multilignes

Cela a du sens car do / end se lit mal dans un one-liner, mais pour les blocs multi-lignes, laisser une fermeture }suspendue sur sa propre ligne est incompatible avec tout ce qui utilise enddans ruby, comme les définitions de module, de classe et de méthode ( defetc. .) et des structures de commande ( if, while,case , etc.)

Le deuxième style, moins fréquemment vu, est connu sous le nom de sémantique, ou " Weirich Braces ", proposé par le regretté grand rubisiste Jim Weirich:

  • Utiliser do endpour les blocs procéduraux
  • Utiliser des accolades { }pour les blocs fonctionnels

Cela signifie que lorsque le bloc est évalué pour sa valeur de retour , il doit être chaînable et les {}accolades ont plus de sens pour le chaînage de méthodes.

D'un autre côté, lorsque le bloc est évalué pour ses effets secondaires , alors la valeur de retour n'a aucune conséquence, et le bloc "fait" simplement quelque chose, donc cela n'a pas de sens d'être enchaîné.

Cette distinction dans la syntaxe donne une signification visuelle à l'évaluation du bloc et indique si vous devez ou non vous soucier de sa valeur de retour.

Par exemple, ici, la valeur de retour du bloc est appliquée à chaque élément:

items.map { |i| i.upcase }

Cependant, ici, il n'utilise pas la valeur de retour du bloc. Il est de fonctionnement de la procédure, et faire un effet secondaire avec elle:

items.each do |item|
  puts item
end

Un autre avantage du style sémantique est que vous n'avez pas besoin de changer d'accolades pour faire / terminer simplement parce qu'une ligne a été ajoutée au bloc.

En guise d' observation, les blocs fonctionnels par coïncidence sont souvent une seule ligne, et les blocs procéduraux (par exemple config) sont multilignes. Ainsi, suivre le style Weirich finit par ressembler presque au style Rails.

Andrew Vit
la source
1

J'ai utilisé le style Weirich pendant des années, mais je m'en suis éloigné pour toujours utiliser des accolades . Je ne me souviens pas avoir jamais utilisé les informations du style de bloc, et la définition est un peu vague. Par exemple:

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

Sont-ils procuduels ou fonctionnels?

Et la chose du nombre de lignes est tout simplement inutile à mon avis. Je sais s'il y a une ou plusieurs lignes et pourquoi devrais-je modifier le style simplement parce que j'ai ajouté ou supprimé des lignes?

iGEL
la source