C’est un langage de script orienté objet et interprété.
Il effectue directement les appels système en traitant les chaines de caractères au moyen de fonctions développées et des expressions régulières.
Il est non déclaratif et non typé.
Sa syntaxe est simple et se rapproche de celle de langages de scripts existants.
Il est orienté objet.
Pour vérifier que l’interpréteur est présent sur le système, il suffit de taper la commande : ruby -v
Welcome to Darwin! [coruscant:~] chris% ruby -v
ruby 1.6.7 (2002-03-01) [powerpc-darwin6.0]
[coruscant:~] chris%
L'option -e permet d’exécuter un programme Ruby sur une seule ligne (one liner).
[coruscant:~] chris% ruby -e 'print "Bonjour a tous\n"'
Bonjour a tous
[coruscant:~] chris%
Traditionnellement, un programme Ruby sera stocké dans un fichier auquel l’interpréteur fera référence. Nous allons éditer un fichier () et y stocker le programme vu précédemment :
print "Bonjour a tous\n"
puis l’exécuter au moyen de la commande : ruby
[coruscant:~/ruby] chris% ruby
Bonjour a tous
[coruscant:~/ruby] chris%
Nous venons d’entrer dans le monde de Ruby.
Nous allons mettre en évidence une particularité de ruby qui est d’effectuer des calculs avec des entiers de précision infinie. Pour cela, nous allons écrire un programme récursif permettant de calculer n!
En Ruby, cette fonction s’écrit :
def fact(n) if n == 0 1 else n * fact(n-1)
end
end
Contrairement à Perl ou Javascript, Ruby me matérialise pas la fonction comme un bloc entre accolades.
Par contre, comme Perl, il renvoie systématiquement comme valeur de retour la dernière valeur évaluée. Le programme permettant de calculer et d’afficher la factorielle d’un nombre est :
def fact(n) if n == 0 1 else n * fact(n-1) end end n=5 print fact(n),"\n" |
Ce texte sera sauvegardé dans un fichier et exécuté.
[coruscant:~/ruby] chris% ruby
120
[coruscant:~/ruby] chris%
Transformons légèrement ce programme afin qu’il récupère son argument sur la ligne de commande.
Comme dans le cas de Perl, l’argument se retrouvera dans une liste nommée ARGV[0]. La fonction to_i permet par ailleurs de convertir en entier une chaîne de caractères. Le nouveau programme fac devient :
def fact(n) if n == 0 1 else n * fact(n-1) end end n= ARGV[0].to_i print fact(n),"\n" |
Et son exécution donne comme résultat :
[coruscant:~/ruby] chris% ruby 5
120
[coruscant:~/ruby] chris%
Explications :
def fact(n) | Def est la déclaration qui va nous permettre de définir une méthode. Elle s’appellera fact et aura un argument , la variable désignée sous le nom de n. |
if n==0 | L’instruction if va permettre de tester une condition, ici le fait que la valeur de la variable n est égale à zéro (n==0). |
1 | Si c’est le cas, alors cette ligne sera évaluée. La valeur renvoyée par la fonction étant systématiquement la dernière valeur évaluée, la valeur 1 sera retournée. |
else | |
n * fact(n-1) | La valeur de la variable n est différente de zéro, on rappelle la méthode fact afin de calculer n * (n-1) ! |
end | Le premier end termine l’instruction if. |
end | Le second end termine la fonction. |
n=5 | Ici commence le programme proprement dit. On définit une variable qui contient la valeur de la factorielle à calculer. |
print fact(n),"\n" | On en demande l’impression. |
Notons au passage que la seconde version du programme lisait la valeur de l’argument sur la ligne de commande. Les instructions étaient :
n= ARGV[0].to_i | Ici comme en perl, ARGV est une liste qui contient l’ensemble des arguments de la ligne de commande. Ces arguments étant récupérés sous la forme de chaîne de caractères, il est indispensable pour effectuer un calcul de procéder à une conversion, d’où la méthode to_i qui transforme une chaîne de caractères en une valeur entière. |
print fact(n),"\n" | On en demande l’impression. |
Comme dans le cas de perl, les arguments de la ligne de commande se récupèrent par l’intermédiaire de la liste ARGV, par contre, contrairement à perl, la conversion chaîne de caractères <-> entier n’est pas automatique. D’où l’utilisation de la méthode to_i qui permet de procéder à cette conversion.
Un code bien écrit doit être documenté, les annotations dans les marges sont à ce sujet fondamentales car elles permettent
de mettre en évidence le travail effectué par le programme ligne par ligne si nécessaire.
Nul n’est capable de comprendre le code écrit par une tierce personne. L’auteur lui même peut avoir du mal à relire sa réalisation et à retrouver l’état d’esprit dans lequel il se trouvait au moment de la conception.
Il est aussi important de noter que le remède peut être pire que le mal, un commentaire en contradiction avec le code, ou placé au mauvais endroit peuvent être pires que pas de commentaires du tout.
Par définition, le code se doit d’être clair. Il ne faut pas croire que le commentaire est la panacée et sera toujours le remède à un code incompréhensible.
La convention utilisée par ruby est commune aux langages de script :
Undièse (#) indique le début d'un commentaire, tout ce qui le suit jusqu'à la fin de la ligne sera ignoré par l'interpréteur.
Les commentaires longs, sur plusieurs lignes, seront pour leur part encadrés par les deux lignes :
=begin
. . .
=end
print "Debut du programme.\n" print "Commentaire sur une ligne.\n" # Cette ligne est un commentaire print "Commentaire sur plusieurs lignes.\n" =begin ------------------------------------- Commentaire du programme ecrit pour mettre en evidence le fait que cette suite de ligne est ignoree par l'interpreteur. ------------------------------------- =end print "Fin du programme.\n" |
Exécution :
[coruscant:~/ruby] chris% ruby Debut du programme.
Commentaire sur une ligne.
Commentaire sur plusieurs lignes.
Fin du programme.
[coruscant:~/ruby] chris%
C’est un petit programme qui fait partie de la distribution de Ruby, it est écrit en Ruby et il permet une saisie de code directement au clavier en affichant le résultat au fur et à mesure.
Si vous ne disposez pas de ce programme, en voici le source :
line = ''
indent=0 print "ruby> " while TRUE
l = gets if not l
break if line == '' else line = line + l if l =~ /,\s*$/ print "ruby| " next end
if l =~ /^\s*(class|module|def|if|case|while|for|begin)\b[^_]/ indent += 1 end
if l =~ /^\s*end\b[^_]/ indent -= 1 end
if l =~ /\{\s*(\|.*\|)?\s*$/ indent += 1 end if l =~ /^\s*\}/ indent -= 1 end if indent > 0 print "ruby| " next end end begin
print eval(line).inspect, "\n"
rescue
$! = 'exception raised' if not $! print "ERR: ", $!, "\n"
end break if not l
line = '' print "ruby> "
end
print "\n"
Il vous suffit de l’éditer, de le sauver sous le nom eval .rf et de le soumettre à l’interpréteur. C’est cette facilité que nous allons utiliser maintenant. Ainsi, si nous reprenons le premier programme que nous avons réalisé : print "Bonjour a tous\n" au moyen de cet outil, nous obtenons :
[coruscant:~/ruby] chris% ruby ruby> print "Bonjour a tous\n"
Bonjour a tous
nil
ruby> exit
[coruscant:~/ruby] chris%
L’instruction print va générer la ligne : Bonjour a tous.
Le nil de la ligne suivante représente la dernière évaluation réalisée.
Comme il n’y a pas de distinction entre les instructions et les expressions, évaluer un bout de programme revient à l'exécuter comme c’est le cas pour Perl.
Le nil indique que l’instruction print n’a pas rendu de valeur significative.
A remarquer l’invite de commande "ruby>".
Par la suite, seront indifférament utilisées les présentations au moyen de cet outil ou les programmes édités sur fichier et
exécutés directement par l’interpréteur.
Les programmes nécessitant un source un peu long seront de préférence présentés conne un fichier soumis à l’interpréteur.
Les démonstrations simples qui ne demandent que peu de lignes de code seront soumises à .
Une chaîne de caractères est un ensemble de caractères qui se présente entre deux simples quottes ou entre deux doubles quottes.
ruby> "Bonjour a tous" "Bonjour a tous"
ruby> 'bonjour tout le monde' "bonjour tout le monde" ruby>
Comme dans Perl, dans une chaîne située entre doubles quottes le mécanisme de substitution est activé, c’est à dire que tous les caractères d’échappement seront interprétés, alors que les simples quottes désactivent le mécanisme de substition.
ruby> print "Bonjour\nMonsieur\n" Bonjour Monsieur nil ruby> print 'Bonjour\nMonsieur\n',"\n" Bonjour\nMonsieur\n nil ruby> '\n' "\\n" ruby> "\n" "\n" ruby> "Il y a #{15*3} personnes" "Il y a 45 personnes" ruby> var="Bonjour" "Bonjour" ruby> "#{var} Madame." "Bonjour Madame." ruby> |
Comme Javascript, Ruby propose la concaténation de chaînes au moyen de l’opérateur + et comme Perl in permet
d’effectuer la multiplication de chaînes au moyen de l’opérateur *.
ruby> "Bonjour "+"Monsieur" "Bonjour Monsieur" ruby> "Trois " * 3 "Trois Trois Trois " ruby> a="Bon" "Bon" ruby> b="jour" "jour" ruby> c=" " " " ruby> d="Monsieur." "Monsieur." ruby> salut=a+b+c+d "Bonjour Monsieur." ruby> |
Il est aussi possible de jouer avec la multiplication des chaînes. Soit à générer la chaine « aaaa bbb ».
Cette chaine est constituée de la chaîne « a » répétée quatre fois, suivie de la chaine espace (« ») suivie de la chaine « b » répétée trois fois.
Il ne reste donc qu’à réécrire en ruby ce que nous venons de dire pour obtenir le résultat souhaité.
ruby> vara="a" "a" ruby> varb="b" "b" ruby> ch=vara * 4 + " " + varb * 3 "aaaa bbb" ruby> |
Par ailleurs, Ruby considère les caractères comme des entiers rendant ainsi possible l’indexation d’une chaime de caractères. La numérotation des indices commençant à 0.
ruby> mot="Bonjour" "Bonjour" ruby> mot[0] 66 ruby> mot[1] 111 ruby> |
Le code ASCII du caractère B est bien 66 (42 hexadécimal) et celui de o est bien 111 (6F hexadécimal).
Par ailleur, il est intéressant de noter que, si l’indice est positif, il indique une exploration de la chaîle de la gauche vers la droite, alors que si les indices sont négatifs, l’exploration de la chaîne se fait de la droite vers la gauche.
L’extraction d’une sous chaîne se fait simplement en indiçant la chaîne par plusieurs valeurs.
Dans l’exemple qui suit, nous partons d’une chaine contenue dans une constante puis nous en extrayons des sous chaines successives.
Il faut bien noter que la première sous chaine qui est récupérée chiffres[3] donne comme résultat 51 (33 hexadécimal) correspondant au code ASCII du chiffre 3 alors que les sous chaines comportant plus d’un élément donnent comme résultat les caractères correspondant.
Ceci est du à la remarque qui a été faite plus haut, à savoir que les caractères représentant les éléments d’une chaine sont considéres comme des entiers.
ruby> chiffres="0123456789" "0123456789" ruby> x=chiffres[3] 51 ruby> zero=chiffres[0,1] "0" ruby> huitneuf=chiffres[-2,2] "89" ruby> uncinq=chiffres[1..5] "12345" ruby> unsix=chiffres[1..-4] "123456" ruby> vide=chiffres[-2..3] nil ruby> deuxcinq=chiffres[-8..-5] "2345" ruby> |
Comparaisons de chaînes.
false ruby> chiffres[-8..-5]==chiffres[2,4] true ruby> chiffres[1]==chiffres[-1] false ruby> |
Et maintenant nous allons réaliser un programme afin de jouer avec la machine.
Cette dernière va choisir un nombre et nous devons le deviner en posant des questions successives. Les réponses qui nous serons fournies seront « Plus grand » si le nombre que nous proposons est inférieur à celui qu’à choisi la machine, plus petit dans le cas contraire.
Dés que le nombre aura été trouve, le programma se termine.
Ce programme sera réalisé comme tout programme source au moyen d’un éditeur de texte. Il sera ensuite stocké dans un fichier afin d’être soumis à l’interpréteur ruby.
Le programme aura l’allure suivante :
#Programme secret = rand(255) print "quel est le nombre inferieur a 256 auquel je pense? " while proposition = ! if proposition.to_i == secret print "Bravo, tu as gagne!\n" break else if proposition.to_i > secret print "Plus petit!\n" else print "Plus grand!\n" end end print "On recommence? " end |
Son exécution donne le résultat :
[coruscant:~/ruby] chris% ruby quel est le nombre inferieur a 256 auquel je pense? 100 Plus petit! On recommence? 50 Plus petit! On recommence? 25 Plus grand! On recommence? 30 Plus grand! On recommence? 40 Plus grand! On recommence? 45 Plus petit! On recommence? 42 Plus grand! On recommence? 43 Plus grand! On recommence? 44 Bravo, tu as gagne! [coruscant:~/ruby] chris% |
Ce programme met en évidence l’utilisation de la boucle while.
Les instructions présentes entre les deux mots clé while et end seront répétées tant que le condition spécifiée sera vraie.
Dans le programme que nous allons détailler ci dessous, cette condition est représentée par la lecture d’une chaîne de
caractères sur l’entrée standard.
Ruby, lorsqu’on lui demande l’évaluation en tant que valeur logique d’un scalaire (chaîne de caractères ou valeur
numérique), renvoie « faux » si il s’agit d’une chaîne vide ou de la valeur numérique 0 et « vrai » dans tous les autres cas.
Il faut noter que Ruby comme Perl lit tous les caractères qui lui sont proposée sur <STDIN>, le caractère de fin de ligne fait donc partie intégrante de la ligne lue.
Ainsi, une ligne pour laquelle l’utilisateur n’aura donné aucune information à l’exception du « retour » ne sera pas vide car elle contiendra le caractère \n.
Seule la ligne contenant le caractère de fin de fichier (^D) sera physiquement vide et donc évaluée à « faux ».
Ainsi, l’usager qui ne veut plus jouer pourra terminer le programme de manière anticipée en tapant ^D en lieu et place d’une valeur numérique.
[coruscant:~/ruby] chris% ruby
quel est le nombre inferieur a 257 auquel je pense? 100 Plus petit!
On recommence? [coruscant:~/ruby] chris%
Une autre remarque sur l’utilisation de la méthode chop. Comme en perl, la méthode chop permet de supprimer le dernier caractère d’une chaîne (généralement le \n).
En ruby, certaines méthodes peuvent se terminer par un point d’exclamation ! ou un point d’interrogation ?.
Le point d’exclamation que l’on prononce « bang » met en évidence une méthode potentiellement destructrice qui va altérer la valeur de l’objet concerné.
En résumé, la méthode chop! Va directement modifier la chaîne concernée alors que la méthode chop (sans le bang) va travailler sur une copie de l’objet.
ruby> c="abcdef" "abcdef" ruby> c.chop! "abcde" ruby> c "abcde" ruby> "abcd" ruby> c "abcde" ruby> resultat "abcd" ruby> |
Par ailleurs, une méthode qui se termine par un point d’interrogation que l’on prononce « euh » indique qu’il s’agit d’une méthode de type prédicat dont la valeur de retour est soit « vrai » soit « faux ».
secret = rand(255) | Ici, on tire un nombre aléatoire dans l’intervalle 0..255 |
print "quel est le nombre inferieur a 256 auquel je pense? " | Puis on pose la question à l’usager. |
while proposition = | La boucle while prend comme condition la méthode qui permet de lire une chaîne de caractères sur l’entrée standard (<STDIN>) effectuera une lecture tant que la chaîne ne sera pas vide. |
! | Comme la chaîne de caractères qui vient d’être lue Contient le caractère de fin de ligne (\n)on le supprime au moyen de la méthode chop. |
if proposition.to_i == secret | On teste alors la valeur du nombre proposé par Rapport a celui qui a été choisi par le programme. |
print "Bravo, tu as gagne!\n" | Si les deux nombres sont égaux, c’est gagné. On le dit |
break | Et on termine le programme. |
else | Dans le cas contraire. |
if proposition.to_i > secret | On donnera à l’usager une indication sur la place du Nombre qu’il vient de proposer par rapport à celui Qu’il doit découvrir. |
print "Plus petit!\n" | Soit il est plus petit. |
else | |
print "Plus grand!\n" | Soit il est plus grand. |
end | Fin du if interne (if proposition.to_i > secret) |
end | Fin du if externe (if proposition.to_i == secret) |
print "On recommence? " | Le nombre n’a pas été trouvé, on repose la question. |
end | Fin du while. |
Comme Perl ou Javascript, Ruby traite les expressions régulières. Le but étant de tester si une chaîne de caractères correspond à un certain modèle. Un certain nombre de caractères ont une signification bien spécifique, ce sont :
Caractère | Signification |
[ ] | spécification d'intervalle (par ex: [a-z] indique une lettre dans l'intervalle a à z) |
\w | lettre ou chiffre; équivaut à [0-9A-Za-z] |
\W | ni lettre ni chiffre |
\s | caractère espace; équivaut à[ \t\n\r\f] |
\S | caractère non espace |
\d | chiffre; équivaut à [0-9] |
\D | non-chiffre |
\b | backspace (0x08) (seulement dans un intervalle) |
\b | limite de mot (sauf dans un intervalle) |
\B | limite autre que de mot |
* | zero, 1 ou n occurrences de ce qui précède |
+ | 1 ou n occurrences de ce qui précède |
{m,n} | au moins m et au plus n occurrences de ce qui précède |
? | Au plus une occurrence de ce qui précède; équivaut à {0,1} |
| | alternative: soit ce qui précède soit ce qui suit |
( ) | groupe |
En ruby, comme en Perl, une expression régulière est généralement encadrée par des slashs (/).
Par ailleurs, pour soumettre une expression régulière à une variable contenant une chaîne de caractères, on utilise un opérateur spécifique =~ qui fournira comme réponse l’emplacement du début du modèle dans la chaine si il a été repére ou nil dans le cas contraire.
Soit par exemple à chercher une chaîne construite sue le modèle suivant :
? Elle doit commencer par une lettre de l'intervalle a-d (a,b,c ou d)
? Puis doivent apparaître les deux lettres 'hr'
? Ensuite doit être présente une lettre prise dans l'intervalle e-i ou dans l'intervalle x-z (e,f,g,h,i,x,y ou z)
Le modèle de recherche de cette chaîne sera : /^[a-d]\w*hr\w*[e-ix-z]/
ruby> def expr(mot) ruby| (mot =~ /^[a-d]\w*hr\w*[e-ix-z]/) != nil ruby| end nil ruby> expr "bonjour" false ruby> expr "chrysantheme" true ruby> expr "chrysalide" true ruby> |
Rappelons qu’un nombre hexadécimal se représente sous la forme Oxhh…h, c’est à dire le chiffre 0 suivi de la lettre x en majuscule ou en minuscule, suivi du nombre hexadécimal dans lequel les lettres peuvent apparaître indifféremment en majuscule ou en minuscule.
L’expression régulière qui validera la représentation d’un nombre hexadécimal est : /0[xX][0-9A-Fa-f]+/
ruby> def hexa (val) ruby| (val =~ /0[xX][0-9A-Fa-f]+/) != nil ruby| end nil ruby> hexa "2002" false ruby> hexa "0X20G34" true ruby> hexa "0x20FAB4DC" true ruby> |
Une liste est un ensemble de scalaires compris entre deux crochets [ ] et séparés par des virgules.
ruby> liste = [1,2,3,4,5,6,7,8,9]
[1, 2, 3, 4, 5, 6, 7, 8, 9] ruby>
Une liste peut êtr concaténée à une autre liste, multipliée par un scalaire exactement comme pour une chaîne de caractères.
ruby> chiffres = [0,1,2,3,4,5,6,7,8,9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
ruby> voyelles = ["a","e","i","o","u"]
["a", "e", "i", "o", "u"]
ruby> liste = chiffres + voyelles
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "e", "i", "o", "u"]
ruby> liste = voyelles * 2
["a", "e", "i", "o", "u", "a", "e", "i", "o", "u"] ruby>
Une liste est indicée de la même manière que dans perl au moyen d’un entier liste[i].
ruby> chiffres = [0,1,2,3,4,5,6,7,8,9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ruby> chiffres[1] 1 ruby> chiffres[3..8] [3, 4, 5, 6, 7, 8] ruby> chiffres[-2,2] [8, 9] ruby> chiffres[-2..-1] [8, 9] ruby> |
Comme pour les chaînes de caractères, les indices négatifs indiquent un déplacement à partir de la fin de la liste.
Les deux opérateurs split et join permettent comme en perl ou en javascript de convertir une chaîne de caractères en une liste ou inversement de concaténer les élements d’une liste pour former une chaîne de caractères.
ruby> chiffres = [0,1,2,3,4,5,6,7,8,9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] ruby> chaine = ("-") "0-1-2-3-4-5-6-7-8-9" ruby> ch = chaine.split("-") ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] ruby> |
C’est une méthode d’accès spécifique aux éléments d’un ensemble. Chaque élément est repéré, non pas par un indice spécifiant son emplacement dans une liste ordonnée, mais par une clé spécifique.
La définition d ‘un hash diffère quelque peu de ce qui a été vu, tant en Perl que en javascript.
Il est important de ne pas considérer un hash comme une table ou une liste au sens ou nous venons de le voir.
Le repérage d’un objet se fait au moyen d’une clé calculée à partir de la chaine de caractères qui a permis l’identification de la valeur.
La notion d’ordre, au sens liste du terme, n’apparaît dons pas dans le cas d’un hash.
ruby> couleur = {"rouge"=> 100, "vert"=> 175, "bleu"=> 150} {"bleu"=>150, "rouge"=>100, "vert"=>175} ruby> couleur["bleu"] 150 ruby> couleur["vert"] 175 ruby> couleur["rouge"] 100 ruby> couleur {"bleu"=>150, "rouge"=>100, "vert"=>175} ruby> |
Ajouter une valeur.
ruby> couleur {"bleu"=>150, "rouge"=>100, "vert"=>175} ruby> couleur["jaune"] = 90 90 ruby> couleur["mauve"] = 50 50 ruby> couleur {"jaune"=>90, "bleu"=>150, "rouge"=>100, "mauve"=>50, "vert"=>175} ruby> |
Ainsi qu’il a été dit, l’ordre dans lequel les divers éléments apparaissent n’est absolument pas celui dans lequel ils ont été introduits.
Nous allons détailler maintenant les structures de contrôle dont dispose le langage.
Cette instruction permet de tester une suite de conditions. Elle a tendance a ressembles au switch de java mais permet de faire beaucoup plus de choses.
ruby> i=8 8 ruby> case i ruby| when 1,3,5,7,9 ruby| print "Chiffre impair\n"
ruby| when 2,4,6,8 ruby| print "Chiffre pair\n" ruby| end Chiffre pair nil
ruby>
Pour comprendre le fonctionnement précis de la structure case, il est indispensable de présenter et de définir le fonctionnement d’un opérateur spécifique ===.
Cet opérateur est un opérateur relationnel permettant te tester plusieurs objets simultanément.
En restant dans l’orientation objet, === aura une interprétation qui sera variable en fonction de la nature de l’objet.
Prenons par exemple le programme suivant et exécutons le.
print "Donnez moi une chaine de caracteres : " while proposition = ! case proposition when 'bonjour'
print "La chaine est en minuscules.\n"
when /[BONJOUR]/
print "Elle contient des majuscules.\n" end
print "Donnez moi une chaine de caracteres : " end
Nous obtenons le résultat suivant :
[coruscant:~/Ruby] chris% ruby Donnez moi une chaine de caracteres : bonjour La chaine est en minuscules.
Donnez moi une chaine de caracteres : boNjour Elle contient des majuscules.
Donnez moi une chaine de caracteres : ^D
[coruscant:~/Ruby] chris%
Case, nous venons de le dire, utilise l’opérateur relationnel pour effectuer ses tests.
Dans ces conditions, le premier test when ‘bonjour’ teste si la chaine contenue dans la variable considérée est exactement le
chaîne de caractères ‘bonjour’.
Par contre, pour ce qui est du second test when:/[BONJOUR]/ l’interprétation qui en est faite est le test de la réussite ou de l’échec de l’application d’une expression régulière.
Il est ainsi possible de tester si une valeur est comprise dans un intervalle donné.
ruby> i=3 3 ruby> (1..5)===i true ruby> |
C’est un moyen simple de construire des boucles non bornées.
Nous en avons déjà vu l’utilisation lors des boucles de lecture.
Le corps de la boucle sera effectué tant que la condition spécifiée dans le while sera vraie.
while condition
instruction 1 instruction 2
.
.
.
instruction n end
Comme en perl, while peut servir pour contrôler une unique instruction.
ruby> i=0 0
ruby> print "#{i+=1}\n" while i<5
1
2
3
4
5
nil
ruby>
Permet de tester une condition négative.
Le corps de la boucle sera effectué tant que la condition spécifiée dans le until sera fausse.
until condition
instruction 1 instruction 2
.
.
.
instruction n end
De la même manière que unless est le complément du until, unless est l’instruction complémentaire du if. Elle permettra d’exécuter le contenu d’un bloc si la condition spécifiée est fausse.
unless condition
instruction 1 instruction 2
.
.
.
.
instruction n end
La boucle for permet d’explorer les divers objets d’un ensemble en effectuant une itération pour chacun d’entre eux. for i in ensemble
instruction 1 instruction 2
.
.
.
.
instruction n end
ruby> for i in (1..5) ruby| print i,"\n" ruby| end 1 2 3 4 5 1..5 ruby> |
L’ensemble spécifié peut être n’inporte quel objet.
ruby> for i in [1,5..7,3.14,"alpha"] ruby| print i,"\n" ruby| end 1 5..7 3.14 alpha [1, 5..7, 3.14, "alpha"] ruby> |
Profitons de cet exercice pour introduire une nouvelle notion. Il est possible en ruby de tester le type d’une variable au moyen de la méthode type. Ainsi, si i est in entier, i.type va retourner le chaîne de caractères ‘fixnum’.
Nous pouvons appliquer cette méthode aux divers éléments de la boucle for que nous venons d’écrire.
ruby> for i in [1,5..7,3.14,"alpha"] ruby| print i,"\t(",i.type,")\n" ruby| end 1 (Fixnum) 5..7 (Range) 3.14 (Float) alpha (String) [1, 5..7, 3.14, "alpha"] ruby> |
L’instruction print fait apparaître une tabulation (\t). Cette instruction aurait pu s’écrire aussi : print "#{i}\t(#{i.type})\n"
Comme en perl et en javascript, une boucle peut être interrompue en cours d’exécution. Ruby nous propose quatre moyens d’effectuer cette rupture.
break pour terminer la boucle. next pour terminer l’itération courante et en commencer une nouvelle. redo recommence l’itération courante. return termine simultanément la boucle courante, et la méthode qui la contient en retournant un argument.
Un itérateur est un concept courant dans les langages orienté objet. Nous avons vu comment au moyen d’une boucle for on pouvait procéder à l’exploration des divers éléments d’un ensemble. Cette exploration est aussi possible au moyen des itérateurs.
Exemple, pour une chaîne de caractères, l’itérateur permettra de procéder à la récupération de chacun des
caractères constituant la chaîne en question.
Exemple avec le type string
ruby> "abc".each_byte{|x| printf "<%c>", x}; print "\n"
<a><b><c>
nil
ruby>
each_byte est une méthode qui va permettre de procéder à une suite d’itérations sur les divers éléments qui constituent la
chaîne de caractères. Chacun etant stocké dans la variable locale x.
En fait, cette même opération aurait très bien pu s’écrire sous une forme plus classique :
ruby> s="abc" "abc" ruby> i=0 0 ruby> while i<s.length ruby| printf "<%c>\n", s[i] ruby| i=i+1 ruby| end <a> <b> <c> nil ruby> |
Il faut toutefois remarquer que l’itérateur est plus simple d’utilisation et pourra fonctionner même si la classe String doit être ultérieurement modifiée.
Un autre itérateur, each_line va explorer une chaîne en considérant comme séparateur le \n représentatif de la fin de ligne.
ruby> t="Un\nDeux\nTrois\nQuatre\nCinq\n" "Un\nDeux\nTrois\nQuatre\nCinq\n" ruby> t.each_line{|x| print x}
Un
Deux
Trois
Quatre
Cinq
"Un\nDeux\nTrois\nQuatre\nCinq\n" ruby>
En fait, tout ce qui, dans les langages traditionnels se fait au moyen d’itérations classiques peut être réalisé plus simplement au moyen des itérateurs.
Notons au passage que, en ruby, l’instruction for réalise l’itération au moyen d’un each.
Or, appliqué à une chaîne, l’instruction each est «équivalente à un each_line.
Ainsi, la boucle :
t ="Un\nDeux\nTrois\nQuatre\nCinq\n" for x in t
print x
end
produira le résultat suivant :
ruby> t ="Un\nDeux\nTrois\nQuatre\nCinq\n" "Un\nDeux\nTrois\nQuatre\nCinq\n" ruby> for x in t ruby| print x ruby| end Un Deux Trois Quatre Cinq "Un\nDeux\nTrois\nQuatre\nCinq\n" ruby> |
Le contrôle retry utilisé dans une boucle permet de rééxécuter l’itération courante à son début.
ruby> c="Faux" "Faux" ruby> for i in 1..5 ruby| print i ruby| if i == 3 and c == "Faux" ruby| c = "Vrai" ruby| print "\n" ruby| retry ruby| end ruby| end; print "\n" 123 12345 nil ruby> |
La commande yield (fournir) permet de passer le contrôle au bloc de code qui est associé à l’itérateur dans lequel il ,est
défini.
Ainsi, dans l’exemple suivant, on définit un itérateur ‘repeter’ qui permet d’exécuter un bloc de code ; le nombre de répétitions étant la valeur passée en argument.
ruby> def repeter (n) ruby| while n > 0 ruby| yield ruby| n-=1 ruby| end ruby| end nil ruby> repeter (3) {print "Bonjour\n"} Bonjour Bonjour Bonjour nil ruby> |
De la même manière, la commande ‘retry’ permet de construire un itérateur qui simule un while.
ruby> def tant_que (condition) ruby| return if not condition ruby| yield ruby| retry ruby| end nil ruby> i=0;tant_que (i<5) {print i; i+=1;print "\n"} 0 1 2 3 4 nil ruby> |
Ainsi donc, un itérateur est attaché à un certain type de données. Ce qui signifie que chaque fois qu’un nouveau type de données est défini, il est comode de définir en parallèle les itérateurs qui lui sont associés.
De manière classique, tout problème de programmation est vu sous l’angle de structures de données et des diverses procédures qui sont chargées de manipules ces structures. Dans ces conditions, les données sont passives.
Le problème vient du fait que la programmation est réalisée par des humains qui n’ont en tête à un instant donné qu’une vue limitée et parcellaire du problème global.
Lorsqu’un projet se développe, son noyau peut grossir démesurément au point qu’il deviendra vite impossible d’en assurer le suivi. C’est alors que de petites erreurs de programmation cachées font leur apparition dans le noyau. La maintenance peut alors tourner au cauchemar car toute correction d’une erreur est susceptible d’en provoquer une ou plusieurs nouvelles.
Lorsqu’on passe à la programmation orientée objet, on peut déléguer tout le travail fastidieux et répétitif aux données elles même. Le statut de la donnée passe donc de passif à actif. En quelque sorte, on cesse de considérer une donnée comme un objet sur lequel tout ou presque est permis, mais davantage comme une boite noire, hermétiquement close, sur laquelle on agit au moyen de boutons et d’interrupteurs, possédant des écrans de contrôle et de commande. Il sera impossible de déterminer de l’extérieur le niveau de complexité de cette boite. Il sera interdit de l’ouvrir que ce soit pour la consulter ou pour la modifier. Les seules interventions qui seront autorisées seront celles qui passeront par l’intermédiaire du panneau de commande. Ainsi, une fois la boite hermétiquement scellée, il ne sera plus nécessaire de savoir comment elle fonctionne.
D’un point de vue un peu simpliste, une méthode peut être définie comme la tâche qu’il est possible de demander à un objet d’accomplir. Comme en JavaScript ; la méthode d’un objet sera invoquée au moyen d’un point. L’objet etant à gauche et la méthode à droite.
ruby> x="abcdef" "abcdef" ruby> x.length 6 ruby> |
Intuitivement, on a créé un objet chaîne de caractères et on demande à cet objet de retourner sa longueur. On invoque la
méthode length appliquée à l’objet x.
Selon le type de l’objet auquel elle est appliquée, la méthode length aura des interprétations différentes, voire même aucune interprétation.
ruby> x="abcdef" "abcdef" ruby> x.length 6 ruby> t=["abcd","123","xyz"] ["abcd", "123", "xyz"] ruby> t.length 3 ruby> z=1 1 ruby> z.length :32: (eval):1: undefined method `length' for 1:Fixnum (NameError) |
Manifestement, la méthode length appliquée à une chaine va retourner la longueur de la chaine en question, alors que la même méthode appliquée à une table va en retourner le nombre d’éléments.
Une erreur est générée qi la methode lengts est appliquée à un objet numérique.
ruby> t=["abcde","123456","wxyz"] ["abcde", "123456", "wxyz"] ruby> t.length 3 ruby> t[0].length 5 ruby> t[1].length 6 ruby> t[2].length 4 ruby> |
Un objet est donc capable d’interpréter une méthode en fonction de ce qu’il sait être. On appelle polymorphisme cette
propriété caractéristique des langages objets.
Nous avons aussi noté qu’une erreur sera générée dés qu’un objet reçoit une demande qu’il est incapable de satisfaire. Bien qu’il soit inutile de savoir comment une méthode donnée est exécutée, il est indispensable de savoir à tout moment si elle est ou non applicable à un objet donné.
Si la méthode nécessite une liste d’arguments, ils seront fournis entre parenthèses.
Objet.methode(argument1,argument2,….,argumentN)
Ruby possède une variable spéciale ‘self’ permettant de se référer à l’objet qui appelle la méthode.
En programmation orientée objet, on appellera classe toute catégorie d’objet. Les objets appartenant à une classe étant pour
leur part appelés instances de cette classe.
Fabriquer un objet c’est définir les caractéristiques de la classe à laquelle cet objet va appartenir, puis créer une instance.
ruby> class Homme ruby| def nom ruby| print "Tout homme a un nom\n" ruby| end ruby| end nil ruby> |
Nous définissons une classe simple Homme qui comporte une méthode simple nom.
ruby> #<Homme:0x146434>
ruby>
La classe Homme étant créée, il est possible de l’utiliser pour fabriquer un homme spécifique que nous allons appeler Français . La méthode new s’applique à toute classe pour en créer une nouvelle instance.
L’instance Français de la classe Homme étant créée, il est maintenant possible de se référer aux méthodes qui ont été définies lors de la création de la classe :
ruby> Tout homme a un nom
nil
ruby>
On appellera instanciation la création d’une nouvelle instance dans une classe donnée.
Il n’est pas possible de faire référence au constructeur de la classe. Ainsi
ruby> class Homme ruby| def nom ruby| print "Tout homme a un nom\n" ruby| end ruby| end nil ruby> :32: (eval):1: undefined method `nom' for Homme:Class (NameError) |
Par contre, il est possible pour tester les méthodes d’une classe de créer un objet éphémère anonyme qui disparaîtra aussitôt.
ruby> class Homme ruby| def nom ruby| print "Tout homme a un nom\n" ruby| end ruby| end nil ruby> ().nom Tout homme a un nom nil ruby> Tout homme a un nom nil |
En effet, n’ayant pas de nom, l’objet créé sera immédiatement détruit par le ramasse miettes (garbage collector) de l’interpréteur.
La classification des objets est généralement hiérarchique.
Si nous prenons l’exemple d’une classe que nous appellerions les mammifères. Il sera possible de subdiviser la classe des mammifères en sous classes, par exemple les humains, les primates et les quadrupèdes. Chacune de ces sous classes héritant des caractéristiques des mammifères. Par exemple, la caractéristique des mammifères est d’avoir des mamelles, cela signifie que toutes les sous classes héritent de cette propriété.
La sous classe représentative des primates aura par exemple en plus la caractéristique d’être des quadrumanes.
ruby> class Mammifere ruby| def mammelle ruby| print"Un mammifere a des mammelles.\n" ruby| end ruby| end nil ruby> class Primate<Mammifere ruby| def quadrumane ruby| print "Un primate a quatre mains\n" ruby| end ruby| end nil |
Nous avons ici défini une classe Mammifère et une méthode mamelle.
Dans cette classe mammifère nous définissons une sous classe Primate qui hérite des propriétés et des méthodes de la classe mammifère mais crée une nouvelle méthode quadrumane.
ruby>
#<Primate:0x146024>
Ainsi, l’objet gorille est un objet appartenant à la classe Primate elle même sous classe de la classe mammifère dont il
possède toutes les caractéristiques.
Il est donc tout aussi naturel d’appliquer à l’objet gorille la méthode mamelle, dont il a hérité de la classe mammifère :
ruby> gorille.mammelle Un mamlifere a des mammelles. nil
Ou la méthode quadrumane décrite dans la classe primate elle même.
ruby> gorille.quadrumane Un primate a quatre mains nil
Il existe toutefois des cas ou la sous classe ne doit pas hériter des caractéristiques de la classe supérieure.
Comme illustration, prenons une classe oiseau qui aura un certain nombre de caractéristiques dont le fait de voler.
class Oiseau def vole print "Un oiseau ca vole.\n" end def plumes print "Un oiseau ca a des plumes.\n" end end print "Appel de la methode :\n" print "\nAppel de la methode Pigeon.plumes:\n" Pigeon.plumes |
L’exécution de ce programme donne le résultat suivant :
[coruscant:~/ruby] chris% ruby Appel de la methode :
Un oiseau ca vole.
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
[coruscant:~/ruby] chris%
Par contre, il existe des situations particulières pour lesquelles une sous-classe ne doit pas hérites des propriétés de la supae-classe. certaines propriétés de la super-classe. Ainsi par exemple, il existe une famille d’oiseaux coureurs (les ratites) qui ne savent pas voler, qui sont tout de même des oiseaux. Si nous reprenons la classe Oiseau que nous venons de voir et que nous désirons déclarer un oiseau, il va falloir demander à ce qu’il hérite de toutes les propriétés de la classe oiseau, mais redéfinir la méthode vole.
class Oiseau def vole print "Un oiseau ca vole.\n" end def plumes print "Un oiseau ca a des plumes.\n" end end class Ratites<Oiseau def vole print "Desole, je ne vole pas, je cours.\n" end end print "Creation de l'objet Pigeon ().\n" print "Appel de la methode Pigeon.plumes:\n" Pigeon.plumes print "\nAppel de la methode :\n" print "\nCreation de l'objet Nandou ().\n" print "Appel de la methode Nandou.plumes:\n" Nandou.plumes print "\nAppel de la methode :\n" |
Lorsqu’on l’exécute, ce programme donne le résultat suivant :
[coruscant:~/ruby] chris% ruby Creation de l'objet Pigeon ().
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
Appel de la methode :
Un oiseau ca vole.
Creation de l'objet Nandou ().
Appel de la methode Nandou.plumes:
Un oiseau ca a des plumes.
Appel de la methode :
Desole, je ne vole pas, je cours.
[coruscant:~/ruby] chris%
Ainsi que nous venons de le voir, il est possible dans une sous-classe, de changer le comportement des instances en redéfinissant les méthodes de la super-classe.
Au lieu de de remplacer totalement la méthode de la super-classe il est aussi possible de la compléter.
class Oiseau def vole print "Un oiseau ca vole.\n" end def plumes print "Un oiseau ca a des plumes.\n" end end class Ratites<Oiseau def vole super print "Oui, mais moi je ne vole pas, je cours.\n" end end print "Creation de l'objet Pigeon ().\n" print "Appel de la methode Pigeon.plumes:\n" Pigeon.plumes print "\nAppel de la methode :\n" print "\nCreation de l'objet Nandou ().\n" print "Appel de la methode Nandou.plumes:\n" Nandou.plumes print "\nAppel de la methode :\n" |
Ce programme donne le résultat suivant :
Creation de l'objet Pigeon ().
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
Appel de la methode :
Un oiseau ca vole.
Creation de l'objet Nandou ().
Appel de la methode Nandou.plumes:
Un oiseau ca a des plumes.
Appel de la methode :
Un oiseau ca vole.
Oui, mais moi je ne vole pas, je cours.
La directive ‘super’ permet aussi de passer de nouveaux arguments à la méthode originale.
Considérons le programme suivant.
Dans ce programme, nous faisons passer un argument à la méthode.
Cet argument, qfin de ne pas inutilement compliquer est la chaîne de caractères qui sera imprimée par l’ordre print qui constitue la méthode.
Dans ces conditions, l’appel de la méthode () doit impérativement présenter une liste d’appel.