On peut écrire des blocs en débutant par “begin” et en finissant par “end”, ou en écrivant des méthodes qui commencent par “do” et terminent par “end”. Mais d’autres manières d’utiliser les blocs existent.

Des blocs en tant que paramètre

Observons cet exemple :

mon_tableau = ['france','allemagne','italie']
mon_tableau.each {|pays| puts "Je suis allé en " + pays + "."}
→ Je suis allé en france.
Je suis allé en allemagne.
Je suis allé en italie.

Je crée un tableau avec 3 entrées. Ensuite, j’applique la méthode .each qui va prendre un à un les éléments du tableau. Entre accolades, j’ai un bloc de code où chaque élément du tableau est passé en paramètre. Et ensuite j’effectue une action avec ce paramètre.
Donc, pour la première valeur “france”, j’applique le code “Je suis allé en ” puis je rajoute le paramètre “france” puis un point “.”

On aurait aussi pu écrire :

mon_tableau = ['france','allemagne','italie']
mon_tableau.each do |pays|
puts "Je suis allé en " + pays + "."
end

On remplace les accolades par le duo do/end.
L’avantage des accolades et de mettre le code sur une seule ligne. On aurait pu créer une méthode mais ça aurait été inutile, étant donné que l’on peut créer un bloc sur une seule ligne.

L’objet Proc

Les Proc sont des blocs de code passés en variables. Au lieu de passer en paramètre un simple String par exemple, on passe en paramètre un bloc de code entier. Un exemple :

mon_proc = Proc.new {|transport| puts "Je préfère le " + transport + "."}
mon_proc.call("train")
→ Je préfère le train.

Je crée un objet Proc en ajoutant en paramètre un bloc de code (entre les accolades). Ce bloc de code contient la variable |transport| qui sera utilisée lorsqu’on appellera le bloc de code.
Donc, à la ligne suivante, j’appelle cet objet Proc avec la méthode .call et je passe en paramètre “train” qui devient la variable de mon bloc de code.

Sauvegarde du contexte

La suite est magique :

def meilleur_buteur(championnat)
  Proc.new {|joueur| puts "Le meilleur buteur de " + championnat + " est " + joueur + "."}
end
france = meilleur_buteur("Ligue 1")
angleterre = meilleur_buteur("Premier League")
 
france.call("Gignac") → Le meilleur buteur de Ligue 1 est Gignac.
angleterre.call("Anelka") → Le meilleur buteur de Premier League est Anelka.
france.call("Cavenaghi") Le meilleur buteur de Ligue 1 est Cavenaghi.

Ce qui est magique, c’est que l’on définit une seule fois la championnat dans lequel on est. En effet, je n’ai écrit qu’une seule fois “Ligue 1″ alors qu’il apparaît deux fois par la suite.
En fait, le paramètre “championnat”, qui aurait dû être détruit dès lors que l’on a quitté la méthode “meilleur_buteur”, a été sauvegardé par l’objet Proc. En fait, un objet Proc conserve le contexte dans lequel il a été créé et peut y accéder à tout moment.
Lorsqu’on a appelé la méthode “meilleur_buteur” avec le paramètre “Ligue 1″, on a créé un objet Proc, et ce dernier a gardé en mémoire le paramètre “Ligue 1″.

Une alternative au Proc : lambda

On peut remplacer Proc.new par lambda.

mon_proc = lambda {|nom| puts "Je m'appelle #{nom}."}
mon_proc.call("Jeremy")
→ Je m'appelle Jeremy.

Apparemment, rien de bien différent. Mais il y a des subtilités. Par exemple, si l’on passe trop de paramètre à un lambda, il renvoie une erreur, alors qu’un Proc saura très bien se débrouiller.
Autre différence notable : un Proc sortira de la méthode dans laquelle il est si il retourne une valeur, alors qu’un lambda continuera dans la méthode.

def methode_lambda
  mon_lambda = lambda{ return "Je sors du lambda!"}
  return "Je ne sors pas du lambda!"
end
 
def methode_proc
  mon_proc = Proc.new{ return "Je sors du Proc!"}
  mon_proc.call
  return "Je ne sors pas du Proc!"
end
 
puts methode_lambda → Je ne sors pas du lambda!
puts methode_proc → Je sors du Proc!

Dans ma “methode_lambda”, “mon_lambda” retourne une valeur, mais continue. Et on arrive au “Je ne sors pas du lambda!”.
Dans ma “methode_proc”, “mon_proc” renvoie une valeur et juste après, on sort de la méthode. Ce qui fait que la ligne “Je ne sors pas du Proc!” n’est pas appelée.

Dernière alternative au Proc : yield

A part Proc et lambda, il existe une seule autre alternative : le yield. En gros, ça veut dire “céder le passage”. Comme ce n’est pas très clair, un petit exemple :

def bonjour
  puts "Bonjour."
  yield
  puts "Comment allez-vous ?"
end
 
bonjour{puts "Je m'appelle Jeremy."}
→ Bonjour.
Je m'appelle Jeremy.
Comment allez-vous ?

En créant ma méthode “bonjour”, j’ai mis 2 phrases et 1 yield. Ensuite, en appelant ma méthode, j’ai mis en paramètre un bloc de code. Donc dans le processus de ma méthode “bonjour” on a :

  1. j’inscris “Bonjour.”
  2. j’appelle le yield
  3. ma méthode “bonjour” cède le passage à mon yield qui appelle le bloc de code {puts “Je m’appelle Jeremy.”}
  4. on retourne dans la méthode “bonjour” et on inscrit “Comment allez-vous ?”

Voici un exemple plus avancé, avec une boucle while :

def separateur(mon_tableau)
  iterateur = 0
  while(iterateur ‹ mon_tableau.length)
    yield(mon_tableau[iterateur])
    iterateur += 1
  end
end
 
mon_tableau = %w(je mets des virgules partout)
separateur(mon_tableau) {|mot| print mot + ", "}
→ je, mets, des, virgules, partout,

Alors, voici le truc. J’ai ma méthode separateur qui prend comme argument “mon_tableau”. Ensuite, je mets mon iterateur à zéro parce que les clés des tableaux commencent à zéro. Puis je boucle autant de fois qu’il y a d’éléments dans le tableau.
Ce qui est particulier est que dans cette boucle j’ai un yield avec comme paramètre l’élément du tableau sur lequel on est. En dehors de la méthode, je crée un tableau avec %w (très pratique).

Le plus intéressant est lorsque j’appelle ma méthode. J’ai le nom de ma méthode (”separateur”) suivi de mon tableau en paramètre (”mon_tableau”). Et à la suite, j’ajoute un bloc de code avec comme variable “mot”. Et en fait, cette variable prend comme valeur “mon_tableau[iterateur]” à chaque passage.

Différence entre Proc et yield

Je vois une différence entre les deux :

  • lorsqu’on crée un Proc, on crée le bloc de code qui sera exécuté. Puis lorsqu’on appelle un Proc, on choisit la variable à être utilisée dans le bloc de code
  • lorsqu’on crée un yield, on choisit (éventuellement) la variable qui sera utilisée dans le bloc de code. Puis lorsqu’on appelle le yield, on crée le bloc de code qui sera exécuté.

Je trouve vraiment que Ruby a dans sa syntaxe beaucoup de classe. Ca tombe bien : on en parle demain.