Dans un chapitre précédent on a vu que l'utilisation de blocks permettait d'effectuer certaines opération dans le cas de certaines méthodes, comme par exemple la méthode each
[1, 2, 4].each do |n|
puts n
end
Mais que se passe-t-il si on souhaite utiliser des blocks dans une méthode que l'on définit ? Il suffit alors d'utiliser yield
. yield permet d'éxécuter le module qui serait passer à la méthode que l'on souhaite éxécuté
def demo(nombre)
yield(nombre)
end
puts demo(2) { |nombre| nombre + 10 }
La méthode définie renverra au block le nombre passé en paramètre. Dès qu'une méthode possède un yield
elle devra systématiquement être utilisé avec un block au risque de renvoyer une erreur.
Les Procs
Le problème des blocks, c'est qu'ils ne peuvent être stocké dans une variable. Aussi, si nous avons plusieurs méthodes auxquelles on souhaite passer une même logique, on est obligé de se répéter. Heureusement, les procs permettent d'éviter se problème là. Un Proc
se définit de la manière suivante.
mon_proc = Proc.new do |number|
puts number * 2
end
[1, 2, 4].each(&mon_proc)
Pour créer un proc on utilisera la méthode Proc.new
avec un block. On pourra alors le stocker dans une variable pour le réutiliser plus tard. En revanche, il n'est pas possible de l'utiliser directement dans les méthodes qui attendent un block (comme la méthode each
par exemple. Il faudra alors convertir le Proc en Block en utilisant le &
devant le nom de la variable.
Un Proc
est un type de variable particulier (objet) comme les entiers, les chaines de caractère ou autre. Il disposent donc de différentes méthodes, dont la méthode call
qui permet de l'appeller avec les paramètre souhaités. Ainsi, de multiplesProc
peuvent être passé en paramètre à une méthode, ce qui n'était pas possible avec les Blocks.
def ma_methode(nombre, proc_1, proc_2)
if nombre.even?
proc_1.call
else
proc_2.call
end
proca = { puts "Salut" }
procb = { puts "Aurevoir" }
ma_methode(2, proca, procb)
Enfin, il est aussi possible de faire la conversion vue tout à l'heure dans l'autre sens en converissant un block passé en paramètre en Proc
def ma_methode(&proc)
proc.call
end
ma_methode { puts "Salut" }
Le &
permet ici de convertir de manière automatique le block en Proc.
Les lambdas
Il existe un type de Proc
particulier, les lambdas, qui s'écrivent de la manière suivante
# syntaxes courtes
ma_variable = lambda { |n| puts n }
ma_variable = ->(n) { |n| puts n }
# syntaxe longue
ma_variable = lambda do |n|
puts n
end
A première vu, un lambda ressemble étrangement à un Proc
mais quelque différences subtiles existent justifiant leur utilisation.
Premièrement, un lambda se comporte comme une méthode, le mot clef return
permet de quitter le block et rend le contrôle à la méthode qui l'a appellé
def ma_method_avec_lambda
lambda { return "a" }.call
return "b"
end
def ma_method_avec_proc
Proc.new { return "a" }.call
return "b"
end
puts ma_method_avec_lambda # b
puts ma_method_avec_lambda # a
Pour cette raison, on préfèrera souvent utiliser les lambda
pour éviter les surprise lors de certains retour.
La seconde différence est la vérification du nombre d'argument. Un Proc peut être appellé malgré un nombre d'argument différent et renverra alors la valeur nil
Proc.new { |a,b| b.inspect }.call(1)
# nil
lambda { |a,b| b.inspect }.call(1)
# ArgumentError: wrong number of arguments (1 for 2)
Voila pour les différences entre ces différentes options. Il faudra donc choisir suivant les cas d'utiliser l'une ou l'autre de ces options.