web-dev-qa-db-fra.com

Meilleure explication des blocs Ruby?

Quelle est la meilleure explication des blocs Ruby que vous pouvez partager?

À la fois l'utilisation et l'écriture de code qui peut prendre un bloc?

39
Blankman

J'offre ma propre explication de cette réponse , légèrement modifiée:

Les "blocs" dans Ruby ne sont pas les mêmes que les termes de programmation généraux "bloc de code" ou "bloc de code".

Imaginez un instant que le code suivant (invalide) Ruby fonctionne réellement:

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"

Bien que ce code ne soit pas valide, son intention - transmettre du code à une méthode et demander à cette méthode d'exécuter le code - est possible de différentes manières Ruby. L'une de ces méthodes est "Blocs" .

Un bloc dans Ruby est très, très semblable à une méthode: il peut prendre quelques arguments et exécuter du code pour ceux-ci. Chaque fois que vous voyez foo{ |x,y,z| ... } ou foo do |x,y,z| ... end, ce sont des blocs qui prennent trois paramètres et exécutent le ... sur eux. (Vous pourriez même voir que la méthode upto ci-dessus passe un bloc.)

Parce que les blocs sont une partie spéciale de la syntaxe Ruby, chaque méthode peut passer un bloc. Que la méthode utilise ou non le bloc dépend de la méthode. Par exemple:

def say_hi( name )
  puts "Hi, #{name}!"
end

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

La méthode ci-dessus reçoit un bloc prêt à émettre une insulte, mais comme la méthode n'appelle jamais le bloc, seul le message Nice est imprimé. Voici comment nous appelons le bloc à partir d'une méthode:

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.

Nous utilisons block_given? pour voir si un bloc a été transmis ou non. Dans ce cas, nous avons renvoyé un argument au bloc; c'est à votre méthode de décider quoi passer au bloc. Par exemple:

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?

C'est juste une convention (et une bonne, et une que vous souhaitez prendre en charge) pour certaines classes pour passer l'instance qui vient d'être créée au bloc.

Ce n'est pas une réponse exhaustive, car elle ne couvre pas la capture de blocs en tant qu'arguments, la façon dont ils gèrent l'arité, la non-éclaboussure des paramètres de bloc, etc., mais a l'intention de servir de blocs-sont- Introduction de Lambdas.

36
Phrogz

Les blocs Ruby sont un moyen de créer Proc objets qui représentent du code qui peut être utilisé par un autre code. Les objets Proc sont des instructions entre accolades {} (Ou do...end Phrases pour les blocs multilignes, qui ont une priorité plus faible que les accolades) qui peuvent éventuellement prendre des arguments et renvoyer des valeurs (par exemple {|x,y| x+y} ). Les procs sont objets de première classe et peuvent être construits explicitement ou atteints implicitement comme pseudo-arguments de méthode:

  1. Construction en tant qu'objet Proc (ou en utilisant le mot clé lambda):

    add1 = Proc.new {|x| x+1} # Returns its argument plus one.
    add1.call(1) # => 2
    
  2. Passé en tant que pseudo-argument de méthode, soit en utilisant explicitement l'opérateur spécial de sucre de syntaxe du dernier argument &, Soit en utilisant implicitement une paire block_given?/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.
    

Le deuxième formulaire est généralement utilisé pour modèles de visiteurs ; les données peuvent être transmises aux arguments de bloc spéciaux comme arguments aux méthodes call ou yield.

27
maerics

De Pourquoi le guide (poignant) de Ruby :

Tout code entouré d'accolades est un bloc.

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

Avec les blocs, vous pouvez regrouper un ensemble d'instructions afin qu'elles puissent être transmises à votre programme. Les accolades frisées donnent l'apparence de pinces de crabe qui ont saisi le code et le tiennent ensemble. Lorsque vous voyez ces deux tenailles, rappelez-vous que le code à l'intérieur a été pressé en une seule unité. C’est comme une de ces petites boîtes Hello Kitty qu’elles vendent au centre commercial, remplies de minuscules crayons et de papier microscopique, toutes entassées dans un étui transparent scintillant qui peut être dissimulé dans votre Palm pour des opérations stationnaires secrètes. Sauf que les blocs ne nécessitent pas autant de strabisme. Les accolades peuvent également être échangées contre les mots do et end, ce qui est bien si votre bloc est plus long qu'une ligne.

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

Les arguments de bloc sont un ensemble de variables entourées de caractères de canal et séparées par des virgules.

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

Les arguments de bloc sont utilisés au début d'un bloc.

{ |x,y| x + y }

Dans l'exemple ci-dessus, | x, y | sont les arguments. Après les arguments, nous avons un peu de code. L'expression x + y additionne les deux arguments ensemble. J'aime penser aux personnages de pipe comme représentant un tunnel. Ils donnent l'apparence d'une goulotte que les variables glissent vers le bas. (Un x descend l'aigle déployé, tandis que le y croise soigneusement ses jambes.) Cette chute agit comme un passage entre les blocs et le monde qui les entoure. Les variables passent par cette goulotte (ou tunnel) dans le bloc.

20
nunopolonia

Pour toute personne venant à cette question à partir d'un arrière-plan C # (ou d'autres langages vraiment), cela pourrait aider:

Les blocs Ruby sont comme des expressions lambda et des méthodes anonymes en C #. Ce sont ce que C # appelle les délégués (et Ruby appelle Procs), c'est-à-dire que ce sont essentiellement des fonctions qui peuvent être passées en tant que valeurs. Dans les deux Ruby et C #, ils peuvent également se comporter comme des fermetures.

Rubis: { |x| x + 1 }

C #: x => x + 1

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

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

C # et Ruby offrent des manières alternatives d'écrire l'exemple ci-dessus.

Rubis:

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

C #:

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

Dans les deux Ruby et C #, plusieurs instructions sont autorisées. Dans Ruby, la deuxième syntaxe ci-dessus est requise pour cela.

Ces concepts sont disponibles dans de nombreux autres langages qui ont été influencés par les idées derrière la programmation fonctionnelle.

7
Justin

Le livre " Programmation Ruby " a une grande explication des blocs et leur utilisation .

En 1.9+, la liste des paramètres passée dans un bloc est devenue plus sophistiquée, permettant de définir des variables locales:

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

;c,d déclare deux nouvelles variables locales à l'intérieur du bloc, qui ne reçoivent pas de valeurs de l'instruction yield de la routine appelée. Ruby 1.9+ garantit que, si les variables existaient en dehors du bloc, elles ne seront pas écrasées par les variables portant le même nom à l'intérieur du bloc. C'est un nouveau comportement; 1.8 les piétinera .

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

Il y a aussi l'opérateur "splat" *, qui fonctionne dans la liste des paramètres:

do |a,*b| 
  some_stuff
end

Attribuerait la première de plusieurs valeurs à "a", et tout le reste serait capturé dans "b" qui serait traité comme un tableau. Le * pourrait être sur la variable a:

do |*a,b| 
  some_stuff
end

capturerait toutes les variables passées à l'exception de la dernière, qui serait passée à b. Et, comme pour les deux précédents:

do |a,*b,c| 
  some_stuff
end

attribuerait la première valeur à a, la dernière valeur à c et toutes/toutes les valeurs intermédiaires à b.

Je pense que c'est assez puissant et lisse.

Par exemple:

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
6
the Tin Man

Les blocs sont un moyen de regrouper du code dans Ruby. Il existe deux façons d'écrire des blocs. L'un utilise l'instruction do..end et l'autre entoure le code entre accolades: {}. Les blocs sont considérés comme des objets dans le langage de programmation Ruby, et par défaut toutes les fonctions acceptent un argument de bloc implicite.

Voici deux exemples de blocs qui font la même chose:

 2. fois {met 'salut'} 
 2. fois fait 
 Met 'salut' 
 Fin 

Les blocs peuvent recevoir des listes d'arguments séparés par des virgules dans des barres verticales ||. Par exemple:

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

Les blocs (dans Ruby 1.9.2) peuvent avoir explicitement des variables locales:

 x = 'bonjour' 
 2. fois | | x | 
 x = 'monde' 
 met x 
 fin 
 
 => monde 
 => monde 

Les variables locales peuvent être combinées avec des paramètres:

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

Toutes les fonctions peuvent recevoir un argument de bloc par défaut:

 déf deux fois 
 rendement 
 rendement 
 fin 
 
 deux fois {met 'bonjour'} 
 => bonjour 
 => bonjour 

Quelle est la différence entre les blocs do..end et {}? Par convention, les blocs {} sont sur une seule ligne et les blocs do..end s'étendent sur plusieurs lignes, car ils sont chacun plus faciles à lire de cette façon. La principale différence tient cependant à la priorité:

 array = [1,2] 
 
 met array.map {| n | n * 10} # put (array.map {| n | n * 10}) 
 => 10 
 => 20 
 
 met array.map do | n | n * 10 end # (met array.map) faire | n | n * 10 end 
 => <Énumérateur: 0x00000100862670> 
3
Pan Thomakos

Les blocs sont des littéraux légers pour des procédures anonymes de première classe avec quelques limitations gênantes. Ils fonctionnent de la même manière dans Ruby car ils fonctionnent dans à peu près tous les autres langages de programmation, modulo les limitations susmentionnées, qui sont:

  • les blocs ne peuvent apparaître que dans les listes d'arguments
  • au plus un bloc peut apparaître dans une liste d'arguments (et ce doit être le dernier argument)
3
Jörg W Mittag