Les méthodes et les blocs
Ce chapitre présente les méthodes (des morceaux de code nommés et réutilisables) et les blocs (des morceaux de code qu'on passe à une méthode). C'est un chapitre fondamental : à partir de maintenant, on peut structurer son code au lieu de tout écrire dans un seul script.
Principe
Jusqu'ici, tout notre code s'exécutait de haut en bas. Si on voulait réutiliser un calcul ou un affichage, il fallait copier-coller les lignes. C'est fragile : si on corrige un bug, il faut le corriger partout.
Une méthode résout ce problème. C'est un ensemble d'instructions qu'on nomme une fois, et qu'on peut appeler autant de fois qu'on veut. On peut aussi lui passer des paramètres pour la rendre flexible.
Un bloc est un morceau de code qu'on passe à une méthode. On l'a déjà utilisé sans le savoir avec .each, .map, .select : le code entre { } ou do...end est un bloc.
Définir une méthode
On crée une méthode avec def, un nom, et end :
def greet
puts 'Bonjour, dresseur !'
end
greet # Affiche : Bonjour, dresseur !
greet # Affiche : Bonjour, dresseur !
def greetdéclare la méthode. Le code à l'intérieur n'est pas exécuté à ce moment-là.greetappelle la méthode. Le code s'exécute à chaque appel.- Le nom d'une méthode suit la convention
snake_case, comme les variables.
Paramètres
Une méthode peut recevoir des paramètres qui la rendent flexible :
def greet_pokemon(name)
puts "Go, #{name} !"
end
greet_pokemon('Pikachu') # Affiche : Go, Pikachu !
greet_pokemon('Dracaufeu') # Affiche : Go, Dracaufeu !
nameest un paramètre. Quand on appelle la méthode, on lui passe une valeur qui remplace ce paramètre.- Si on appelle
greet_pokemonsans argument, Ruby lève une erreur.
Valeurs par défaut
On peut donner une valeur par défaut à un paramètre :
greeting = 'Go': si l'argument n'est pas fourni,greetingvaut'Go'.
Paramètres nommés (keyword)
Pour les méthodes avec plusieurs paramètres optionnels, les paramètres nommés sont plus lisibles :
level:est un paramètre nommé obligatoire (pas de valeur par défaut).type: :normalest un paramètre nommé avec valeur par défaut.- À l'appel, on écrit
level: 25au lieu de juste25. C'est plus long mais bien plus clair quand il y a plusieurs paramètres.
Le splat *args
Le splat collecte un nombre variable d'arguments dans un Array :
def display_team(trainer, *pokemon_names)
puts "Équipe de #{trainer} :"
pokemon_names.each do |pokemon_name|
puts " - #{pokemon_name}"
end
end
display_team('Sacha', 'Pikachu', 'Dracaufeu', 'Tortank')
*pokemon_namesrassemble tous les arguments aprèstrainerdans un Array.- On peut passer 0, 1 ou 100 noms — le splat s'adapte.
Le double-splat **options
Le double-splat collecte les paramètres nommés non déclarés dans un Hash :
def create_pokemon(name, level:, **stats)
pokemon = { name: name, level: level }
return pokemon.merge(stats)
end
pikachu = create_pokemon('Pikachu', level: 25, attack: 55, speed: 90)
p pikachu # => {:name=>"Pikachu", :level=>25, :attack=>55, :speed=>90}
**statsattrapeattack: 55, speed: 90dans un Hash{ attack: 55, speed: 90 }..merge(stats)fusionne ce Hash avec le Hash de base.
Valeurs de retour
Toute méthode Ruby retourne une valeur. On peut la retourner explicitement avec return :
def calculate_damage(attack, defense)
return attack - defense
end
damage = calculate_damage(55, 30)
puts "Dégâts : #{damage}" # Affiche : Dégâts : 25
Sans return, Ruby retourne automatiquement la dernière expression évaluée :
def calculate_damage(attack, defense)
attack - defense
end
Les deux formes fonctionnent. Dans cette série de tutoriels, on utilise return explicitement pour que l'intention soit claire, surtout quand on débute.
return peut aussi servir à sortir prématurément d'une méthode :
def heal(pokemon)
if pokemon[:hp] >= pokemon[:max_hp]
puts "#{pokemon[:name]} a déjà tous ses PV !"
return
end
pokemon[:hp] = pokemon[:max_hp]
puts "#{pokemon[:name]} est soigné !"
end
- Le premier
returnsort immédiatement de la méthode si le Pokémon est déjà au maximum. Le reste du code n'est pas exécuté.
Les blocs en profondeur
On utilise des blocs depuis le chapitre 3 avec .each, .map, .select. Voyons comment créer nos propres méthodes qui acceptent des blocs.
yield — exécuter le bloc
yield permet à une méthode d'exécuter le bloc qu'on lui passe :
def with_announcement(pokemon_name)
puts "--- Début ---"
yield(pokemon_name)
puts "--- Fin ---"
end
with_announcement('Pikachu') do |name|
puts "#{name} utilise Tonnerre !"
end
Affiche :
--- Début ---
Pikachu utilise Tonnerre !
--- Fin ---
yield(pokemon_name)exécute le bloc en lui passantpokemon_namecomme argument.- Le bloc reçoit cet argument dans
|name|. - La méthode contrôle ce qui se passe avant et après le bloc.
block_given? — vérifier si un bloc est passé
def display_pokemon(pokemon)
puts "#{pokemon[:name]} Niv.#{pokemon[:level]}"
if block_given?
yield(pokemon)
else
puts " Type : #{pokemon[:type]}"
end
end
pikachu = { name: 'Pikachu', level: 25, type: :electric, hp: 35, max_hp: 55 }
# Sans bloc : affichage par défaut
display_pokemon(pikachu)
# Avec bloc : affichage personnalisé
display_pokemon(pikachu) do |pokemon|
puts " PV : #{pokemon[:hp]}/#{pokemon[:max_hp]}"
end
block_given?retournetruesi un bloc a été passé,falsesinon.- Cela permet d'avoir un comportement par défaut que l'appelant peut personnaliser.
&block — capturer le bloc en objet
Le préfixe & convertit un bloc en objet Proc, qu'on peut stocker et appeler avec .call :
def find_pokemon(team, &condition)
team.each do |pokemon|
return pokemon if condition.call(pokemon)
end
return nil
end
team = [
{ name: 'Pikachu', level: 25 },
{ name: 'Dracaufeu', level: 36 },
{ name: 'Tortank', level: 40 }
]
result = find_pokemon(team) { |pokemon| pokemon[:level] >= 35 }
puts "Trouvé : #{result[:name]}" # Affiche : Trouvé : Dracaufeu
&conditioncapture le bloc et le transforme en un objet Proc stocké danscondition.condition.call(pokemon)exécute ce Proc avecpokemoncomme argument.- La différence avec
yield: le Proc peut être stocké dans une variable ou passé à une autre méthode.
Proc et lambda
Ruby permet aussi de créer des blocs "autonomes" qu'on stocke dans des variables :
# Lambda (recommandé)
calculate = lambda do |attack, defense|
return attack - defense
end
puts calculate.call(55, 30) # => 25
# Syntaxe raccourcie (stabby lambda)
calculate = ->(attack, defense) { attack - defense }
puts calculate.call(55, 30) # => 25
- Un lambda est un bloc autonome stocké dans une variable. On l'appelle avec
.call. ->() { }est un raccourci pourlambda do ... end.
Il existe aussi Proc.new, qui fonctionne de façon similaire mais avec une différence importante : return dans un Proc sort de la méthode englobante, tandis que return dans un lambda sort seulement du lambda. Pour cette raison, préférer lambda qui est plus prévisible.
On peut passer un lambda à une méthode qui attend un bloc avec & :
high_level = ->(pokemon) { pokemon[:level] >= 35 }
result = team.select(&high_level)
&high_levelconvertit le lambda en bloc pour le passer à.select.
Conclusion
def method_name(params) ... enddéfinit une méthode. Jamais en une seule ligne.- Les paramètres peuvent être obligatoires, avec défaut, nommés (
key:), splat (*args) ou double-splat (**options). - Utiliser
returnexplicitement pour clarifier ce que la méthode retourne. yieldexécute le bloc passé à la méthode.block_given?vérifie sa présence.&blockcapture le bloc en Proc pour le stocker ou le transmettre.- Un lambda est un bloc autonome stocké dans une variable. Préférer
lambdaàProc.new. &variableconvertit un lambda/Proc en bloc pour le passer à une méthode.