Proc & Block

Voir la vidéo
Description Sommaire

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 Procse 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 eachpar 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.

Publié
Technologies utilisées
Auteur :
Grafikart
Partager