Les conditions en Ruby
2013-02-11 #ruby
Le mot clé "if"
Comme dans la plupart des langages, le mot clé if
sert à tester
une condition et il s'emploie de la façon suivante :
if condition
ici_on_fait_quelque_chose...
end
On peut aussi compléter le if
par un then
(un peu
comme en Basic) :
if condition then
ici_on_fait_quelque_chose...
end
Le if "mono-ligne"
La version avec le then
est obligatoire pour écrire des tests
sur une seule ligne de code :
if condition then ici_on_fait_quelque_chose... end
Par exemple :
irb(main):001:0> if 10 > 1 then puts "10 gagne" end
10 gagne
=> nil
Alors que :
irb(main):002:0> if 10 > 1 puts "10 gagne" end
SyntaxError: (irb):2: syntax error, unexpected tIDENTIFIER, expecting keyword_then or ';' or '
'
if 10 > 1 puts "10 gagne" end
^
(irb):2: syntax error, unexpected keyword_end, expecting $end
from C:/Ruby/bin/irb:12:in `<main>'
Dans ce cas, on obtient une erreur qui nous dit qu'il manque le mot clé
then
.
De même, on ne peut pas écrire tout simplement :
irb(main):003:0> if 10 > 1 puts "10 gagne"
irb(main):004:1>
L'indice 1
dans irb(main):004:1>
nous indique
que IRB attend la suite de l'instruction. Et si on ajoute un end
pour "fermer" le if
, on obtient une erreur qui nous dit qu'il
manque le mot clé then
:
irb(main):003:0> if 10 > 1 puts "10 gagne"
irb(main):004:1> end
SyntaxError: (irb):3: syntax error, unexpected tIDENTIFIER, expecting keyword_then or ';' or '
'
if 10 > 1 puts "10 gagne"
^
(irb):4: syntax error, unexpected keyword_end, expecting $end
from C:/Ruby/bin/irb:12:in `<main>'
irb(main):005:0>
Le if en tant que "modificateur"
C'est pourquoi on utilise le if
différement quand on veut
écrire un test sur une seule ligne :
ici_on_fait_quelque_chose... if condition
Ce qui donne :
irb(main):006:0> puts "10 gagne" if 10 > 1
10 gagne
=> nil
C'est déjà plus facile à écrire que if 10 > 1 then puts "10 gagne" end
et c'est aussi plus clair à lire :
> 10 gagne si 10 est supérieur à 1
est plus lisible et naturel que :
> si 10 est supérieur à 1 alors 10 gagne
Le mot clé "else"
Comme dans les autres langages, on peut aussi définir le traitement à
réaliser lorsque la condition n'est pas respectée à l'aide du mot clé
else
:
if condition
ici_on_fait_quelque_chose...
else
et_la_on_fait_autre_chose...
end
Ou :
if condition then
ici_on_fait_quelque_chose...
else
et_la_on_fait_autre_chose...
end
Cela peut aussi s'écrire sur 1 seule ligne, mais là encore on perd beaucoup en lisibilité :
irb(main):017:0> if 10 > 1 then puts "10 gagne" else puts "10 perd" end
10 gagne
=> nil
Le mot clé "elsif"
Quand on a besoin d'enchainer les tests, on peut utiliser le mot clé
elsif
qui correspond au else if
des autres
langages :
if condition_1
ici_on_fait_quelque_chose...
elsif condition_2
la_on_fait_autre_chose...
else
et_la_une_autre_chose...
end
Par exemple :
if 10 > 1
puts "10 gagne"
elsif 10 < 1
puts "10 perd"
else
puts "c'est serré"
end
Si nécessaire, on peut utiliser autant de elsif
que l'on
veut :
if condition_1
ici_on_fait_quelque_chose...
elsif condition_2
la_on_fait_autre_chose...
elsif condition_3
la_une_troisieme_chose...
else
et_la_une_autre_chose...
end
Et dans tous les cas, la partie else
doit toujours être située
après tous les autres tests.
Comme toujours, les elsif
peuvent également être condensés à
condition d'utiliser le then
:
if condition_1 then ici_on_fait_quelque_chose...
elsif condition_2 then la_on_fait_autre_chose...
elsif condition_3 then la_une_troisieme_chose...
else et_la_une_autre_chose...
end
Quand on a l'habitude de la syntaxe else if
des autres
langages, il faut bien faire attention à ne pas se mélanger les pinceaux, parce
que l'utilisation de "else if" en Ruby provoque une imbrication des tests.
Le code Ruby ci-dessous est incorrect :
if 10 > 1
puts "10 gagne"
else if 10 < 1
puts "10 perd"
else
puts "c'est serré"
end
=> syntax error, unexpected $end, expecting keyword_end
On a une erreur car en fait ce code correspond au code suivant :
if 10 > 1
puts "10 gagne"
else
if 10 < 1
puts "10 perd"
else
puts "c'est serré"
end
<-- ici il manque un "end"
Le mot clé "unless"
Quand on veut tester l'opposé d'une condition, on peut utiliser l'opérateur
not
:
if not (condition)
on_fait_quelque_chose...
end
Dans ce cas, on ne fait quelque chose que si la condition n'est pas réalisée :
if not (10 < 1)
puts "10 gagne"
end
Outre le fait qu'on est presque obligé de mettre des parenthèses pour s'y retrouver, ça ne fait pas très lisible selon les critères rubyistes.
C'est là que le mot clé unless
entre en scène :
unless condition
on_fait_quelque_chose...
end
Comme par exemple :
unless 10 < 1
puts "10 gagne"
end
En fait, le mot clé unless
est l'exact opposé du mot clé
if
et on peut faire avec lui tout ce qui est possible avec le
if
:
unless condition then
ici_on_fait_quelque_chose...
end
unless condition
ici_on_fait_quelque_chose...
else
et_la_on_fait_autre_chose...
end
unless condition then ici_on_fait_quelque_chose... end
etc...
Ce qui va décider du choix d'un if
ou d'un unless
à est une
question de fluidité du code : qu'est-ce qui est le plus clair à
comprendre et qu'est-ce qui sonne le mieux à l'oreille ?
Comme pour le if
, le unless
est très intéressant
quand on s'en sert en tant que "modificateur" :
ici_on_fait_quelque_chose... unless condition
Ce qui donne :
irb(main):021:0> puts "10 gagne" unless 10 < 1
10 gagne
=> nil
L'opérateur ternaire "? ... :"
Il existe une autre façon d'écrire des tests "condensés", c'est l'opérateur
ternaire " ?
" qui existe également dans d'autres langages comme
C# ou PHP :
irb(main):022:0> puts 10 > 1 ? "10 gagne" : "10 perd"
10 gagne
=> nil
Après le " ?
" on met l'expression renvoyée lorsque la
condition est vraie et après le " :
" on met l'expression renvoyée
lorsque la condition n'est pas vraie.
condition ? expression_si_vrai : expression_si_faux
Avec l'opérateur ternaire, on ne peut utiliser que des expressions à renvoyer et pas des instructions à exécuter :
irb(main):023:0> puts 10 > 1 ? "10 gagne" : "10 perd"
10 gagne
=> nil
irb(main):024:0> 10 > 1 ? "10 gagne" : "10 perd"
=> "10 gagne"
Alors que :
irb(main):025:0> 10 > 1 ? puts "10 gagne" : puts "10 perd"
SyntaxError: (irb):25: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('
10 > 1 ? puts "10 gagne" : puts "10 perd"
^
(irb):25: syntax error, unexpected ':', expecting $end
10 > 1 ? puts "10 gagne" : puts "10 perd"
^
from C:/Ruby/bin/irb:12:in `<main>'
Donc, le résultat de l'opérateur ternaire est toujours une expression (celle définie pour le cas où la condition est vraie ou bien celle définie pour le cas contraire).
Le if en tant que "expression"
Tout comme l'opérateur ternaire, le mot clé if
peut lui aussi
servir à renvoyer une expression.
Par exemple, en C# on aurait ce code :
var resultat = "";
if (condition_1)
{
resultat = "cas un";
}
else if (condition_2)
{
resultat = "cas deux";
}
else
{
resultat = "autre cas";
}
Console.WriteLine(resultat);
Dont l'équivalent en Ruby serait :
resultat = ""
if condition_1
resultat = "cas un"
elsif condition_2
resultat = "cas deux"
else
resultat = "autre cas"
end
puts resultat
Mais en Ruby, il est quelquefois mieux de renvoyer une valeur depuis
l'instruction if
:
resultat = if condition_1
"cas un"
elsif condition_2
"cas deux"
else
"autre cas"
end
puts resultat
Ou si la variable resultat
ne sert jamais ailleurs :
puts if condition_1
"cas un"
elsif condition_2
"cas deux"
else
"autre cas"
end
C'est un peut comme l'opérateur ternaire ?
mais cela permet de
gérer plus de cas (et pas seulement un vrai / faux) et cela permet aussi
d'exécuter des instructions selon le cas :
puts if condition_1
ici_on_fait_quelque_chose...
"cas un"
elsif condition_2
la_on_fait_autre_chose...
"cas deux"
else
et_la_une_autre_chose...
"autre cas"
end
De façon plus générale, il est bon de savoir qu'en Ruby toutes les instructions sont en fait des expressions.
Le mot clé "case"
Dans certains cas, on a besoin de tester plusieurs fois une même variable en
la comparant à différentes valeurs. On peut faire ça en enchainant toute une
série de if
et de elsif
:
if quoi == valeur_1
ici_on_fait_quelque_chose...
elsif quoi == valeur_2
la_on_fait_autre_chose...
elsif quoi == valeur_3
la_une_troisieme_chose...
else
et_la_une_autre_chose...
end
Mais en général, il vaut mieux simplifier ce genre de code en ayant recours
à la syntaxe case ... when
:
case quoi
when valeur_1
ici_on_fait_quelque_chose...
when valeur_2
la_on_fait_autre_chose...
when valeur_3
la_une_troisieme_chose...
else
et_la_une_autre_chose...
end
Comme pour le if
, un bloc case
est capable de
renvoyer une valeur et on peut donc sans problème écrire le code
suivant :
resultat = case quoi
when 1
"cas un"
when 2
"cas deux"
else
"autre cas"
end
puts resultat
Un when
n'est pas limité à une seule valeur de comparaison. On
peut y faire figurer plusieurs valeurs, à condition de les séparer par des
virgules. Une autre possibilité est d'employer un Range pour tester par rapport
à un ensemble de valeurs.
case quoi
when 0
"zéro"
when 1, 2, 3
"très peu"
when 4..9
"un peu plus"
else
"beaucoup"
end
Comme pour le if
, il est possible d'utiliser une version
"condensée" du case ... when
en associant chaque condition au
mot-clé then
pour séparer la condition en elle-même de son
traitement :
case quoi
when 1 then "cas un"
when 2 then "cas deux"
else "autre cas"
end
Il existe également une autre forme de l'instruction case
dans
laquelle on n'indique pas la valeur à tester après le
case
:
case
when quoi == 1
"cas un"
when quoi == 2
"cas deux"
when quoi < 5
"cas trois ou quatre"
else
"autre cas"
end
Dans cette syntaxe, le when
est suivi de la condition complète
(quoi == 1
) et pas seulement de la valeur à tester. L'avantage,
c'est que cela permet beaucoup plus de variété dans les expressions à tester
(on peut même tester par rapport à d'autres variables). Par contre, ce n'est
pas forcément beaucoup plus lisible qu'une série de if
/
elsif
?
Par rapport au langage C#, le case ... when
du Ruby est assez
proche du switch ... case
:
switch (quoi)
{
case valeur_1:
ici_on_fait_quelque_chose...
break;
case valeur_2:
la_on_fait_autre_chose...
break;
case valeur_3:
la_une_troisieme_chose...
break;
default:
et_la_une_autre_chose...
break;
}
Ils répondent tous les deux au même genre de besoin pour éviter un
enchainement de if
et elsif
/ else if
,
mais ils présentent tout de même une différence assez importante :
- En Ruby, seul le premier
when
dont la condition est vraie est traité. - Alors qu'en C#, tous les
case
pour lesquels la condition est vérifiée sont pris en compte, d'où la nécessité dubreak
pour se limiter au premier cas trouvé.
Et aussi, si on est un peu habitué à C# (et peut-être d'autres langages), il faut faire attention de ne pas se tromper en tapant des "case une_valeur" pour chaque condition au lieu des "when une_valeur" typiques de Ruby.
Les opérateurs || et ||=
Ce n'est pas une condition comme if
, unless
ou
case
, mais c'est quelque chose qu'on rencontre assez souvent dans
en Ruby et qui permet de remplacer l'opérateur ternaire " ? ... :
" quand on veut affecter une valeur par défaut à une variable.
irb(main):026:0> toto = "une valeur"
=> "une valeur"
L'opérateur ternaire " ? ... :
" peut permettre de tester
explicitement si la variable toto est définie et si c'est le cas renvoie cette
valeur toto et sinon renvoie "une autre valeur". La valeur retournée étant
ensuite affectée à la variable toto de départ.
irb(main):027:0> toto = toto ? toto : "une autre valeur"
=> "une valeur"
Dans ce code, la partie " toto ?
" sert à tester si la variable
toto est définie, parce qu'en Ruby, quelque chose qui n'existe pas est
considéré (évalué) comme faux et quelque chose qui existe est considéré comme
vrai.
Étant donné que le test porte uniquement sur le fait que la variable toto
est définie ou non, l'opérateur " ||
" (le or
) permet
de réaliser le même genre de test mais de façon implicite :
irb(main):028:0> toto = toto || "une autre valeur"
=> "une valeur"
L'opérateur " ||=
" permet d'aller encore plus loin dans la
mesure où le test porte sur la même variable que celle à laquelle on va
affecter le résultat :
irb(main):029:0> toto ||= "une autre valeur"
=> "une valeur"
L'opérateur " ||=
" est comme l'opérateur " +=
"
qui permet quant à lui de concaténer les opérateurs " +
" et "
=
" :
irb(main):030:0> i = 10
=> 10
irb(main):031:0> i = 10 + 3
=> 13
irb(main):032:0> i += 3
=> 16
L'intérêt des opérateurs " ||
" et " ||=
" c'est
que lorsqu'ils sont employés avec des variables qui n'ont pas encore été
définies, cela permet de leur attribuer des valeurs par défaut :
irb(main):033:0> tutu = tutu ? tutu : "valeur par défaut 1"
=> "valeur par défaut 1"
irb(main):034:0> titi = titi || "valeur par défaut 2"
=> "valeur par défaut 2"
irb(main):035:0> tata ||= "valeur par défaut 3"
=> "valeur par défaut 3"
irb(main):036:0> puts tutu, titi, tata
valeur par défaut 1
valeur par défaut 2
valeur par défaut 3
=> nil