Alors ça fait quoi yield ?

Si vous fournissez un bloc de code quand vous appelez une méthode, alors au sein de cette méthode il sera possible de prendre le contrôle de ce bloc de code grâce à yield. Votre méthode sera exécutée normalement, une fois le mot-clef yield rencontré, le bloc de code fourni sera exécuté et finalement les instructions de la méthode placées après le yield seront à leur tour exécutées.

Pour vous faire plus facilement une représentation de ce mécanisme, vous pouvez penser yield comme une sorte de callback. Lorsque que le mot-clef yield est rencontré, l'interpréteur sait qu'il doit stopper temporairement l'exécution du corps de la méthode pour aller exécuter le bloc de code qui a été passé juste derrière l'appel de méthode.

Prenons un exemple pour mieux comprendre :

def test_de_yield
  puts "On entre dans le corps de notre méthode"
  puts "Nous allons maintenant appeler yield"
  yield
  puts "Nous sommes revenus dans le corps de la méthode"
end

test_de_yield { puts "Un petit coucou depuis le bloc de code !" }
	

L'exécution de cet exemple nous donne la chose suivante en résultat :

On entre dans le corps de notre méthode
Nous allons maintenant appeler yield
Un petit coucou depuis le bloc de code !
Nous sommes revenus dans le corps de la méthode
	

On voit donc que lorsqu'on appelle la méthode test_de_yield, l'interpréteur fait un saut dans cette même méthode, affiche nos deux premières lignes puis rencontre le mot-clef yield. L'interpréteur va donc comprendre qu'il doit maintenant exécuter le bloc de code qui a été passé à la méthode. Une fois ce bloc terminé, l'interpréteur retourne dans le corps de la méthode, juste après le yield.

On peut passer des arguments à un bloc de code ?

Oui !

Les blocs de code ont de nombreux points communs avec les méthodes. Ce sont tout deux des suites d'instructions qui peuvent être exécutés et qui peuvent attendre des arguments.

Pour envoyer des arguments à un bloc de code, il faut les passer à yield. yield(1,2) vous montre comment passer deux arguments à votre bloc de code.

Le bloc qui est exécuté par notre yield peut donc utiliser ces arguments. Il existe pour cela une syntaxe particulière qui utilise les pipes (||) comme dans l'exemple ci-dessous :

methode_yield do |a, b|
  # le code ici
end 
	
ou
methode_yield { |a,b| # le code ici }
	

Comment utiliser yield et les arguments ?

Prenons un exemple qui ne sert pas à grand chose mais qui a l'avantage de présenter un morceau de code utilisant yield et les arguments :

def yield_aleatoire
  yield(rand(10))
end

yield_aleatoire { |x| puts "Le nombre généré est #{x}"}
	

Notre méthode tire un nombre aléatoire (entre 0 et 9) puis la passe à yield. Ensuite, dans notre appel de méthode, nous affectons ce nombre à la variable x grâce aux pipes puis nous l'affichons.

Mais comment faire si je veux renvoyer une valeur à ma méthode depuis mon bloc de code ?

Il est vrai que les blocs auraient un usage limité s'il n'était pas possible de leurs faire renvoyer une valeur. Fort heureusement, un bloc de code est capable de renvoyer une valeur, cette valeur est celle qui est évaluée en dernier dans le bloc. Ainsi dans la méthode, la valeur de retour du bloc est en fait la valeur de retour de yield. Voyons un exemple :

def retour_yield
  resultat = yield(3)
  puts "Le résultat obtenu est : #{resultat}."
end

retour_yield { |x| x + 2 }  # => Le résultat obtenu est : 5
	

Notre bloc reçoit un argument, l'assigne à la variable x et retourne le résultat de x + 2 (rappelez vous, en Ruby, la valeur de retour est toujours le résultat de la dernière expression évaluée). Ce résultat est ensuite récupéré dans notre méthode en tant que valeur de retour de yield, ce qui nous permet ensuite de l'afficher.

Mais un seul yield c'est nul ! Je peux itérer ?

Et là encore, je vous répond oui !

Toutes les méthodes qui utilisent yield et les blocs sont appelées itérateurs et ce n'est pas pour rien :-). En toute logique, un itérateur est créé pour faire de la répétition, travailler sur une liste ou une collection d'objets. En pratique certaines méthodes qui utilisent yield ne font pas de répétition mais la grande majorité de ces méthodes en font. On peut donc utiliser ces méthodes pour traiter une liste de valeurs et retourner un résultat après traitement de ces valeurs.

Voyons un exemple :

def places_bus(objets)
  objets.each do |o|
    resultat = yield(o)
    if resultat ==  true
      puts "Le bus est pas disponible pour #{o} personnes"
    else
      puts "Le bus n'est pas disponible pour #{o} personnes"
    end
  end
end

USAGERS = [10, 4, 34, 8, 890, 54]
places_bus(USAGERS) { |u| u < 40 }
	

Que faisons-nous dans cet exemple ? Nous avons écris une méthode places_bus qui prend un argument. L'argument attendu est un tableau d'objets. Chaque objet est parcouru puis envoyé à notre bloc grâce à yield. Nous stockons la valeur de retour de yield (qui doit être true ou false pour faire un test et afficher un message différent en fonction du résultat de ce test).

Nous créons ensuite un tableau de nombres puis nous utilisons notre nouvelle méthode pour faire un test simple, si le nombre est supérieur à 40 nous renvoyons false, sinon true. On a donc maintenant un itérateur simple et flexible qui nous permet de vérifier si un bus est assez spacieux pour nos voyageurs ou non. Nous pouvons mettre le test que nous voulons dans notre bloc de code du moment qu'il renvoit soit true, soit false.

Nous avons donc partager le travail entre notre méthode et notre bloc de code, le résultat est obtenu en sautant de l'un à l'autre. Cela peut vous paraître inutile mais le partage du travail entre la méthode et le bloc de code nous permet de gagner en flexibilité puisque nous pourrons faire des vérifications semblables avec une seule méthode en changeant simplement le bloc lors de l'appel de notre méthode.

Si demain vos bus ne prennent la route qu'à partir de 15 personnes, il vous suffira de changer le bloc pour faire vos vérifications et vous n'aurez pas à modifier la méthode :

USAGERS = [10, 4, 34, 8, 890, 54]
places_bus(USAGERS) { |u| u < 40 && u >= 15 }	
	

Conclusion

Vous aurez vu ici que l'utilisation de yield et des blocs de code vous permet de mettre en place des méthodes très dynamiques et multi-usages. Pensez donc à yield lorsque vous souhaitez écrire une méthode assez généraliste, notamment pour une librairie.