C. Aperghis-Tramoni Département d`Informatique

Transcription

C. Aperghis-Tramoni Département d`Informatique
RUBY
C. Aperghis-Tramoni
Département d’Informatique
RUBY ............................................................................................................................................................. 1
C. Aperghis-Tramoni................................................................................................................. 1
C’est quoi Ruby ? ...................................................................................................................... 4
Les débuts en programmation Ruby. ........................................................................................ 5
Les commentaires ...................................................................................................................... 7
Le programme eval.rb ............................................................................................................... 8
Les chaînes de caractères........................................................................................................ 10
Les expressions régulières....................................................................................................... 15
Les listes................................................................................................................................... 17
Les Hashes............................................................................................................................... 18
Structures de contrôle ............................................................................................................. 19
case.....................................................................................................................................................19
while...................................................................................................................................................20
until....................................................................................................................................................20
unless .................................................................................................................................................21
La boucle for.....................................................................................................................................21
Les ruptures de boucle.....................................................................................................................22
Les itérateurs. .......................................................................................................................... 23
La programmation orienté-objet............................................................................................. 26
Les méthodes............................................................................................................................ 27
Les classes................................................................................................................................ 29
L’héritage................................................................................................................................. 31
Redéfinition des méthodes....................................................................................................... 34
Les contrôles d'accès ............................................................................................................... 37
Les méthodes Singleton........................................................................................................... 39
Les modules ............................................................................................................................. 40
Les objets procédures .............................................................................................................. 41
Les variables ............................................................................................................................ 43
Variables globales.................................................................................................................... 44
Variables d'instance ................................................................................................................ 45
Variables locales...................................................................................................................... 46
Constantes de classe ................................................................................................................ 49
Traitement des exceptions: rescue .......................................................................................... 51
Traitement des exceptions: ensure.......................................................................................... 55
Accesseurs................................................................................................................................ 56
Initialisation des objets............................................................................................................ 61
Divers problèmes. .................................................................................................................... 64
Délimiteurs d'instruction.................................................................................................................64
Organisation du code. ......................................................................................................................64
Et en guise de conclusion........................................................................................................ 66
C’est quoi Ruby ?
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 (Prog1.rb) et y stocker le programme vu précédemment :
print "Bonjour a tous\n"
puis l’exécuter au moyen de la commande : ruby test.rb
[coruscant:~/ruby] chris% ruby Prog1.rb
Bonjour a tous
[coruscant:~/ruby] chris%
Nous venons d’entrer dans le monde de Ruby.
Les débuts en programmation 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 fac.rb et exécuté.
[coruscant:~/ruby] chris% ruby fac.rb
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 ./fac.rb 5
120
[coruscant:~/ruby] chris%
Explications :
def fact(n)
if n==0
1
else
n * fact(n-1)
end
end
n=5
print fact(n),"\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.
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).
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.
La valeur de la variable n est différente de zéro, on rappelle la méthode fact afin de calculer n * (n-1) !
Le premier end termine l’instruction if.
Le second end termine la fonction.
Ici commence le programme proprement dit. On définit une variable qui contient la valeur de la
factorielle à calculer.
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
print fact(n),"\n"
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.
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.
Les commentaires
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 comm.rb
Debut du programme.
Commentaire sur une ligne.
Commentaire sur plusieurs lignes.
Fin du programme.
[coruscant:~/ruby] chris%
Le programme eval.rb
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 eval.rb
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 à eval.rb.
Les chaînes de caractères.
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 Devine.rb afin d’être soumis à l’interpréteur ruby.
Le programme Devine.rb aura l’allure suivante :
#Programme Devine.rb
secret = rand(255)
print "quel est le nombre inferieur a 256 auquel je pense? "
while proposition = STDIN.gets
proposition.chop!
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 ./Devine.rb
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.
Devine.rb Fac.rb Prog1.rb eval.rb
[coruscant:~/ruby] chris% ruby Devine.rb
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> resultat=c.chop
"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)
print "quel est le nombre inferieur a 256 auquel je pense? "
while proposition = STDIN.gets
proposition.chop!
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
Ici, on tire un nombre aléatoire dans l’intervalle 0..255
Puis on pose la question à l’usager.
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.
On teste alors la valeur du nombre proposé par
Rapport a celui qui a été choisi par le programme.
Si les deux nombres sont égaux, c’est gagné.
On le dit
Et on termine le programme.
Dans le cas contraire.
On donnera à l’usager une indication sur la place du
Nombre qu’il vient de proposer par rapport à celui
Qu’il doit découvrir.
Soit il est plus petit.
Soit il est plus grand.
Fin du if interne (if proposition.to_i > secret)
Fin du if externe (if proposition.to_i == secret)
Le nombre n’a pas été trouvé, on repose la question.
Fin du while.
Les expressions régulières
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
[]
\w
\W
\s
\S
\d
\D
\b
\b
\B
*
+
{m,n}
?
|
()
Signification
spécification d'intervalle (par ex: [a-z] indique une lettre dans l'intervalle a à z)
lettre ou chiffre; équivaut à [0-9A-Za-z]
ni lettre ni chiffre
caractère espace; équivaut à[ \t\n\r\f]
caractère non espace
chiffre; équivaut à [0-9]
non-chiffre
backspace (0x08) (seulement dans un intervalle)
limite de mot (sauf dans un intervalle)
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
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>
Les listes
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 = chiffres.join("-")
"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>
Les Hashes.
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.
Structures de contrôle
Nous allons détailler maintenant les structures de contrôle dont dispose le langage.
case
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 = STDIN.gets
proposition.chop!
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 relat.rb
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>
while
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>
until
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
unless
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.
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"
Les ruptures de boucle.
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
next
redo
return
pour terminer la boucle.
pour terminer l’itération courante et en commencer une nouvelle.
recommence l’itération courante.
termine simultanément la boucle courante, et la méthode qui la contient en retournant un argument.
Les itérateurs.
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 each.byte 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>
défini.
La commande yield (fournir) permet de passer le contrôle au bloc de code qui est associé à l’itérateur dans lequel il ,est
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.
La programmation orienté-objet.
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.
Les méthodes
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
eval.rb: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.
Les classes.
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> Francais=Homme.new
#<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> Francais.nom
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> Homme.nom
eval.rb: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> (Homme.new).nom
Tout homme a un nom
nil
ruby>Homme.new.nom
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.
L’héritage.
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> gorille=Primate.new
#<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
Pigeon=Oiseau.new
print "Appel de la methode Pigeon.vole:\n"
Pigeon.vole
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 oiseau.rb
Appel de la methode Pigeon.vole:
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 (Oiseau.new).\n"
Pigeon=Oiseau.new
print "Appel de la methode Pigeon.plumes:\n"
Pigeon.plumes
print "\nAppel de la methode Pigeon.vole:\n"
Pigeon.vole
print "\nCreation de l'objet Nandou (Ratites.new).\n"
Nandou=Ratites.new
print "Appel de la methode Nandou.plumes:\n"
Nandou.plumes
print "\nAppel de la methode Nandou.vole:\n"
Nandou.vole
Lorsqu’on l’exécute, ce programme donne le résultat suivant :
[coruscant:~/ruby] chris% ruby oiseau.rb
Creation de l'objet Pigeon (Oiseau.new).
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
Appel de la methode Pigeon.vole:
Un oiseau ca vole.
Creation de l'objet Nandou (Ratites.new).
Appel de la methode Nandou.plumes:
Un oiseau ca a des plumes.
Appel de la methode Nandou.vole:
Desole, je ne vole pas, je cours.
[coruscant:~/ruby] chris%
Redéfinition des méthodes
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 (Oiseau.new).\n"
Pigeon=Oiseau.new
print "Appel de la methode Pigeon.plumes:\n"
Pigeon.plumes
print "\nAppel de la methode Pigeon.vole:\n"
Pigeon.vole
print "\nCreation de l'objet Nandou (Ratites.new).\n"
Nandou=Ratites.new
print "Appel de la methode Nandou.plumes:\n"
Nandou.plumes
print "\nAppel de la methode Nandou.vole:\n"
Nandou.vole
Ce programme donne le résultat suivant :
Creation de l'objet Pigeon (Oiseau.new).
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
Appel de la methode Pigeon.vole:
Un oiseau ca vole.
Creation de l'objet Nandou (Ratites.new).
Appel de la methode Nandou.plumes:
Un oiseau ca a des plumes.
Appel de la methode Nandou.vole:
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 (pigeon.vole) doit impérativement présenter une liste d’appel.
class Oiseau
def vole(param)
print param,".\n"
end
def plumes
print "Un oiseau ca a des plumes.\n"
end
end
print "Creation de l'objet Pigeon (Oiseau.new).\n"
Pigeon=Oiseau.new
print "Appel de la methode Pigeon.plumes:\n"
Pigeon.plumes
print "\nAppel de la methode Pigeon.vole:\n"
Pigeon.vole("Un oiseau ca vole")
print "\nCreation de l'objet Nandou (Ratites.new).\n"
Nandou=Oiseau.new
print "Appel de la methode Nandou.plumes:\n"
Nandou.plumes
print "\nAppel de la methode Nandou.vole:\n"
Nandou.vole("Un ratite ca ne vole pas")
L’exécution de ce programme donne le résultat suivant :
[coruscant:~/ruby] chris% ruby oiseau.rb
Creation de l'objet Pigeon (Oiseau.new).
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
Appel de la methode Pigeon.vole:
Un oiseau ca vole.
Creation de l'objet Nandou (Ratites.new).
Appel de la methode Nandou.plumes:
Un oiseau ca a des plumes.
Appel de la methode Nandou.vole:
Un ratite ca ne vole pas.
[coruscant:~/ruby] chris%
La directive ‘super’ est aussi utilisable pour forcer le passage d’un paramètre constant à une méthode qui en réclame un.
Ainsi, dans la définition d’un ratite, l’appel de la méthode vole (ratite.vole) contraint le passage de la chaîne voulue à la
méthove oiseau.vole.
class Oiseau
def vole(param)
print param,".\n"
end
def plumes
print "Un oiseau ca a des plumes.\n"
end
end
class Ratites<Oiseau
def vole
super ("Un ratite ca ne vole pas")
end
end
print "Creation de l'objet Pigeon (Oiseau.new).\n"
Pigeon=Oiseau.new
print "Appel de la methode Pigeon.plumes:\n"
Pigeon.plumes
print "\nAppel de la methode Pigeon.vole:\n"
Pigeon.vole("Un oiseau ca vole")
print "\nCreation de l'objet Nandou (Ratites.new).\n"
Nandou=Ratites.new
print "Appel de la methode Nandou.plumes:\n"
Nandou.plumes
print "\nAppel de la methode Nandou.vole:\n"
Nandou.vole
L’exécution de ce programme donne le résultat suivant :
[coruscant:~/ruby] chris% ruby oiseau.rb
Creation de l'objet Pigeon (Oiseau.new).
Appel de la methode Pigeon.plumes:
Un oiseau ca a des plumes.
Appel de la methode Pigeon.vole:
Un oiseau ca vole.
Creation de l'objet Nandou (Ratites.new).
Appel de la methode Nandou.plumes:
Un oiseau ca a des plumes.
Appel de la methode Nandou.vole:
Un ratite ca ne vole pas.
[coruscant:~/ruby] chris%
Les contrôles d'accès
Bien que ruby n'a pas de fonctions, seulement des méthodes, il y a plusieurs sortes de méthodes.
Voyons ce qui se passe loraque une méthode n’est pas définie dans une classe, mais au niveau le plus élevé du
programme. On peut considérer qu’une telle méthode est identique à une fonction d’un langage traditionnel.
def carre(n)
n*n
end
print carre(25),"\n"
Résultat :
[coruscant:~/ruby] chris% ruby carre.rb
625
[coruscant:~/ruby] chris%
Dans l’absolu, il semble que cette méthode n’appartienne à aucune classe.
En fait, ruby créée une classe qui est au dessus de toutes les autres et qui est nommée ‘Object’.
Ainsi, tout objet peut utiliser cette méthode comme on invoque une fonction dans un langage traditionnel.
def carre(n)
n*n
end
class Calcul
def puissance_6 (nombre)
carre(nombre) ** 3
end
end
print Calcul.new.puissance_6(10),"\n"
Dans l’exemple ci dessus, la méthode carre, définie au niveau ‘main’ peut sans problèmes être utilisée par la classe ‘calcul’ .
L’exécution en donne la preuve.
[coruscant:~/ruby] chris% ruby puiss.rb
1000000
[coruscant:~/ruby] chris%
Par contre, il est interdit d'appliquer explicitement la méthode à un objet:
def carre(n)
n*n
end
print "Calcul".carre(10),"\n"
Le résultat fait apparaître le message d’erreur.
[coruscant:~/ruby] chris% ruby carre.rb
carre.rb:5: private method `carre' called for "Calcul":String (NameError)
[coruscant:~/ruby] chris%
Ainsi on préservera la nature orientée objet de ruby.
Comme dans tout langage, il est possible de fournir des fonctions, Ce seront en définitive des méthodes dont le receveur
implicite est self.
Nous avons évoqué précédemment la séparation entre la spécification et l’implémentation, en d’autres termes quelle est la
tache qui doit être accomplie et comment elle est accomplie.
En toute logique, k’utilisateur de l’objet ne devrait pas avoir connaissance de son fonctionnement interne, mais seulement de
ce qui entre et de ce qui en sort. Il est donc indispensable que certaines méthodes d’une classe utilisées en interne puissent être
rendues invisibles à un utilisateur.
Dans l'exemple trivial ci-dessous, imaginez que moteur est ce qui travaille dans la classe sans être vu.
class Calcul
def puiss_x(y,x)
print y," puissance ",x," est egal a ",puiss(y,x),"\n"
end
def puiss(y,x)
y ** x
end
private:puiss
end
essai=Calcul.new
print "\nAppel de essai.puiss_x(3,5)\n"
print essai.puiss_x(3,5),"\n"
print "\nAppel de essai.puiss(3,5)\n"
print essai.puiss(3,5),"\n"
Le fait de déclarer la méthode puiss privée (‘private :puiss) la rend invisible à l’utilisateur de la classe.
Toute référence à cette méthode génèrera ainsi une erreur.
[coruscant:~/ruby] chris% ruby puiss.rb
Appel de essai.puiss_x(3,5)
3 puissance 5 est egal a 243
nil
Appel de essai.puiss(3,5)
carre.rb:16: private method `puiss' called for #<Calcul:0x147708> (NameError)
[coruscant:~/ruby] chris%
Clairement, la méthode puiss n’est pas accessible directement par l’utilisateur de l’objet essai.
Seule la méthode puiss_x est autorisée.
(puiss).
Dans ces conditions, il est indispensable de passer par une méthode publique (puiss_x) pour utiliser la méthode privée
Le concepteur de l’objet peut ainsi modifier les méthodes privées sans que l’utilisateur en ait connaissance.
Les méthodes Singleton
Le comportement d’une instance est entièrement définie pas sa classe, lors de la déclaration crée les méthodes qui lui sont
attachées et qui seront celles de tous les objets créés à partir de cette classe.
Dans certains cas, une application peut vouloir en modifier le comportement, par exemple rajouter une métgoide qui ne sera
utilisée que pour un objet précis.
Ce problème peut être résolu en créant une nouvelle classe, c’est ce qui est fait dans la plupart des langages, cette classe
reprenant la totalité de celle précédemment créée en y ajoutant une ou plusieurs méthodes.
En ruby, il est possible de redéfinir ses propres méthodes pour un objet donné rtattaché à une classe existante, on évite
ainsi la création de classes spécifiques dont l’utilité n’est pas justifiée.
On appellera singleton une méthode que l’on rajoute à un objet au moment de sa création à partir d’une classe existante.
class Single
def texte
print "Texte standard de la classe Single.\n\n"
end
end
print "Declaration de test1 (test1=Single.new).\n"
test1=Single.new
print "Appel de la methode texte (test1.texte).\n"
test1.texte
print "Declaration de test2 (test2=Single.new).\n"
print "Avec modification de la methode texte.\n"
test2=Single.new
def test2.texte
print "Texte de la classe single\nmodifie dans la declaration de test2.\n"
end
print "Appel de la methode texte (test2.texte).\n"
test2.texte
L’exécution de ce programme donne :
[coruscant:~/ruby] chris% ruby single.rb
Declaration de test1 (test1=Single.new).
Appel de la methode texte (test1.texte).
Texte standard de la classe Single.
Declaration de test2 (test2=Single.new).
Avec modification de la methode texte.
Appel de la methode texte (test2.texte).
Texte de la classe single
modifie dans la declaration de test2.
[coruscant:~/ruby] chris%
Dans l’exemple qui vient d’être programmé, test1 et test2 appartiennent à la même classe (Single). Cependant, au moment
de la déclaration de test2, la méthode texte a été redéfinie. Les deux objets ont donc des comportements différents. C’est cette
méthode qui n’a été donnée qu’à un objet que l’on appelle un singleton.
On utilisera abondamment ces méthodes singleton pour les éléments de l’interface graphique (Graphic User Interface) dans
lesquelles on fait apparaître des boutons pour lesquels les actions à entreprendre dépendent du bouton que l’on sélectionne.
Les modules
Par définition, un module est semblable à une classe avec les restrictions suivantes :
➤ Il ne peut pas avoir d’instances
➤ Il ne peut pas avoir de sous classes
➤ Il est défini par les mots clé module …. end
Les modules serviront dans un premier temps pour regrouper les constantes et le méthodes en rapport les unes avec les
autres.
Ainsi par exemple le module Math.
ruby> Math.sqrt(5)
2.236067977
ruby> Math::PI
3.141592654
ruby> Math::E
2.718281828
ruby>
L'opérateur :: permet d’indiquer à l'interpréteur quel est le module concerné pour trouver la valeur d'une constante.
Il et aussi possible d’inclure ce module afin d’y référer directement.
ruby> include Math
Object
ruby> sqrt(9)
3.0
ruby> PI
3.141592654
ruby> E
2.718281828
ruby>
Certains langages comme C++ permettent un héritage multiple, c’est à dire d’avoir plusieurs super-classes directes pour une
classe donnée.
Prenons comme exemple un radio réveil à cassettes. Cet objet est simultanément un objet de la classe des indicateurs de
temps, des postes de radio et des lecteurs de cassettes. Il doit donc hériter des caractéristiques de chacune de ces différentes
classes.
Ruby n’offre pas véritablement l’héritage multiple, par contre, il propose une technique différente comme une alternative a
cette propriété. Cette technique est le ‘mixin’.
Nous avons fait remarquer comme préalable qu’un module ne peut ni être instancié, ni être sous-classé. Toutefois, si un
module est inclus dans la définition d’une classe, l’ensemble de ses méthodes seront, par définition, mélangées à celles de la
classe.
On dispose ainsi d’une possibilité d’obtenir certaines propriétés.
Soit une classe qui posséde une méthode ‘each’ le fait d’y mêler par ‘include’ le mode ‘Enumerable’ de la librairie standard
permet d’accéder gratuitement aux méthodes ‘sort’ et ‘find’.
Il est ainsi possible de définir les fonctionnalités de base de l’héritage multiple tout en représentant par un arbre simple les
relations de classe.
Les objets procédures
Afin de faire face à des évènements inattendus, il est parfois indispensable de traiter des lignes de code comme si il
s’agissait de lignes de données.
Pour cela, on pourra créer un nouvel objet, l’objet procédure).
ruby> bonjour = proc {
ruby| print "Bonjour la compagnie\n"
ruby| }
#<Proc:0x146c54>
ruby>
L’objet ainsi créé auquel se réfère ‘bonjour’ peut s’exécuter via sa méthode ‘call’
ruby> bonjour.call
Bonjour la compagnie
nil
ruby>
Mais, il peut tout aussi bien être utilisé comme argument d’une méthode.
bonjour = proc {
print "Bonjour la compagnie\n"
}
def executer (param)
print "On va executer la procedure passee en parametre...\n\n"
param.call
print "\nPrecedure executee...\n"
end
executer (bonjour)
Résultat :
[coruscant:~/ruby] chris% ruby proc.rb
On va executer la procedure passee en parametre...
Bonjour la compagnie
Precedure executee...
[coruscant:~/ruby] chris%
Il existe par ailleurs une méthode ‘trap’ qui nous permet de récupérer certains les évènements du système. Par exemple le
fait de taper Ctrl-C permet de sortir de l’interpréteur.
evenement = proc{ print " <- Ne tentez pas de tuer le programme SVP.\n" }
trap "SIGINT", evenement
while proposition = STDIN.gets
proposition.chop!
if proposition.to_i == 0
print "On finit le programme\n"
break
end
end
Ce programme lit des nombres sur l’entrée standard et se termine normalement si on donne la valeur zéro. On veut éviter
que l’usager termine brutalement son programme au moyen de la commande Ctrl-C. Pour ce faire, on rajoute la commande trap qui
permet dans ce cas d’exécuter la procédure ‘evenement’. L’exécution de ce programme donne donc :
[coruscant:~/ruby] chris% ruby event.rb
1
2
^C <- Ne tentez pas de tuer le programme SVP.
3
0
On finit le programme
[coruscant:~/ruby] chris%
Ceci étant, il est toujours possible si une urgence se produit de tuer le programme au moyen de la commande Ctrl-D.
Il n’est pas indispensable de nommer une procédure trap. L’utilisation d’une procédure annonyme est toujours possible. On
aurait ainsi pu écrire :
trap "SIGINT", proc{ print " <- Ne tentez pas de tuer le programme SVP.\n"}
ou encore plus simplement :
trap "SIGINT", 'print " <- Ne tentez pas de tuer le programme SVP.\n"'
Les variables
Il existe deux grandes familles de langage de programmation :
Les langages déclaratifs dans lesquels toute variable doit être déclarée. Ceci permet de spécifier son type, si elle est ou non
modifiable (variable ou constante) et sa visibilité.
Les langages pour lesquels aucune déclaration n’est nécessaire. Une variable étant automatiquement déclarée dés que son
nom apparaît. C’est le cas de ruby.
Le premier caractère du nom de la variable permet de la caractériser.
Premier caractère du nom
$
@
[a-z]
_
[A-Z]
Type
variable globale
variable d'instance
variable locale
variable locale
constante
Il n’existe que deux exeptions à cette règle, ce sont les deux pseudo-variables :
‘self’ qui désigne l’objet en cours d’exécution.
‘nil’ qui est la valeur sans signification.
D’après les règles qui ont été données ci dessus, les noms devraient représenter des variables locales, mais en fait ‘self’ est
une variable globale qui est maintenue à la bonne valeur par l’interpréteur et nil est une constante, elle sera d’ailleurs affectée à
toute variable non initialisée.
Il est bien entendu impossible d’affecter une quelconque valeur a ces deux variables.
ruby> self
main
ruby> self=proc
eval.rb:32: (eval):1: compile error (SyntaxError)
(eval):1: Can't change the value of self
self=proc
^
[coruscant:~/ruby] chris%
Variables globales
Toute variable sera considérée comme globale si son nom commence par $. Elle pourra être utilisée n'importe où dans le
programme. Avant initialisation, elle sera automatiquement initialisée à nil.
ruby> $valeur
nil
ruby> $valeur=154
154
ruby> $valeur
154
ruby> $valeur+=1
155
ruby>
Il faut se méfier des variables globales. Leur accessibilité les rend modifiables depuis n’importe quelle partie du programme
et leur abus peur poser des problèmes lors de la mise au point. Un programme bien analysé limite leur nombre au strict minimum.
Toutefois, une particularité agréable de ce type de variable est la possibilité qui est offerte de les tracer, c’est à dire
d’invoquer une procédure chaque fois que leur valeur est modifiée.
trace_var :$x, proc{print "$x vient d'etre modifiee, elle vaut maintenant ", $x, "\n"}
for $x in (1..5)
end
[coruscant:~/ruby] chris% ruby trace.rb
$x vient d'etre modifiee, elle vaut maintenant 1
$x vient d'etre modifiee, elle vaut maintenant 2
$x vient d'etre modifiee, elle vaut maintenant 3
$x vient d'etre modifiee, elle vaut maintenant 4
$x vient d'etre modifiee, elle vaut maintenant 5
[coruscant:~/ruby] chris%
Ainsi, une variable globale peut être utilisée comme un déclencheur. Chacune de ses modification lancera une procédure
par exemple la mise à jour d’une interface graphique.Elle sera alors appelée une ‘variable active’.
Il existe comme en perl une série de variables globales prédéfionies. Les principales sont :
Variable
$!
$@
$_
$.
$&
$~
$n
$=
$/
$\
$0
$*
$$
$?
Contenu
Dernier message d'erreur.
Emplacement de l'erreur.
Dernière chaîne lue par gets (variable standard d’entrée).
Numéro de la dernière ligne lue par l'interpréteur.
Dernière chaîne trouvée dans une expression régulière.
Dernière trouvaille de type tableau dans une expression régulière.
La nième trouvaille de la dernière expression régulière (équivaut à $~[n])
Sensibilité (minuscules/majuscules) des recherches par expression régulière.
Séparateur des éléments en entrée.
Séparateur des éléments en sortie.
Le nom du script courant ruby.
La liste des arguments de la ligne de commande.
L'identifiant du process de l'interpréteur (PID).
Code retour du dernier processus fils.
A noter au passage que $_ et $~ bien que portant un nom qui suggère une portée globale sont en fait des variables locales.
Variables d'instance
Une variable sera une variable d’instance si son nom commence par @. Sa portée se limite aux objets auxquels elle se
réfère (self). Deux objets différents appartenant à une même classe peuvent avoir des valeurs différentes dans leurs variables
d’instances dans leurs variables d’instance respectives.
De l’extérieur de l’objet, les variables ne peuvent pas être altérées, elles ne sont même pas visibles. En fait, les variables
d’instance ne sont jamais publiques.
La seule possibilité d’y accéder seront les méthodes explicitement fournies par le programmeur.
Comme les variables globales, jusqu’à leur première affectation elles auront la valeur nil.
Les variables d’instance ne sont pas déclarées. En fait, chacune d’elle est dynamiquement ajoutée à l’oblet au moment de
sa première invocation.
class Instance
def set_1(n)
@inst1 = n
print "Dans Instance : @inst1 = ",@inst1,", inst2 = ",@inst2,"\n"
end
def set_2(n)
@inst2 = n
print "Dans Instance : @inst1 = ",@inst1,", inst2 = ",@inst2,"\n"
end
end
i = Instance.new
i.set_1(2)
i.set_2(4)
print "Dans Main : @inst1 = ",@inst1,", inst2 = ",@inst2,"\n"
Résultat du programme :
[coruscant:~/ruby] chris% ruby inst.rb
Dans Instance : @inst1 = 2, inst2 = nil
Dans Instance : @inst1 = 2, inst2 = 4
Dans Main : @inst1 = nil, inst2 = nil
[coruscant:~/ruby] chris%
Variables locales
Toute variable dont le nom commence par une lettre minuscule ou par un blanc souligné est une variable locale.
Contrairement aux variables globales ou aux variables d’instances elles ne sont pas initialisées à nil.
print "Impression de la variable d'instance @inst non initialisee : \n"
print "
@inst = ",@inst,"\n"
print "Impression de la variable globale $glob non initialisee : \n"
print "
$glob = ",$glob,"\n"
print "Impression de la variable locale loc non initialisee : \n"
print "
loc = ",loc,"\n"
Résultat :
[coruscant:~/ruby] chris% ruby loc.rb
Impression de la variable d'instance @inst non initialisee :
@inst = nil
Impression de la variable globale $glob non initialisee :
$glob = nil
Impression de la variable locale loc non initialisee :
loc.rb:6: undefined local variable or method `loc' for #<Object:0x153cd8> (NameError)
[coruscant:~/ruby] chris%
C’est la première affectation de la variable locale qui joue le rôle de la déclaration, Il est donc indispensable que la première
apparition d’une variable locale soit dans la partie gauche d’un signe d’affectation.
Une référence à une variable locale non initialisée est considérée par l’interpréteur comme la tentative d’accéder à une
méthode qui porte le même nom.
Ceci explique la teneur du message d’erreur qui est donné dans l’exemple ci dessus.
Une variable locale porte dans :
• proc{ ... }
• loop{ ... }
• def ... end
• class ... end
• module ... end
• la totalité du programme si aucun des cas précédents ne s'applique.
defined? est un opérateur qui vérifie si un identificateur est défini. Rappelons qu’une méthode qui se termine par un point
d’interrogation (euh) est de type prédicat, sa valeur de retour est soit « vrai » soit « faux ». la méthode defined? rendra une
description de l'identificateur ce dernier est défini, et nil sinon.
La portée de ‘locid’ est locale à la boucle ce qui signifie qu’il devient indéfini dés qu’on la quitte.
ident = 800
print "Valeur de l'identificateur ident : ",ident, "\n"
print defined? ident,"\n\n"
loop{
locid=1284
print "Dans la boucle locid=",locid, "\n"
break
}
print defined? locid,"\n"
Exécution :
[coruscant:~/ruby] chris% ruby loc.rb
Valeur de l'identificateur ident : 800
local-variable
Dans la boucle locid=1284
nil
[coruscant:~/ruby] chris%
A noter que les objets procédures qui existent dans la même portée se partagent tous les variables locales qui sont
déclarées dans cette portée.
univ=0
p1 = proc{|n|
univ=n
print "Dans la procedure, valeur de la variable : ",univ,"\n"
}
p2 = proc{univ}
print "Appel p1.call(5) : \n"
p1.call(5)
print "Impression de la valeur de la variable, univ = ",univ,"\n"
print "Appel p2.call : ",p2.call,"\n"
Dans ce cas, la variable locale ‘univ’ est partagée par ‘main’ et par les objets procédure ‘p1’ et ‘p2’
[coruscant:~/ruby] chris% ruby loc.rb
Appel p1.call(5) :
Dans la procedure, valeur de la variable : 5
Impression de la valeur de la variable, univ = 5
Appel p2.call : 5
[coruscant:~/ruby] chris%
Il est aussi très important de noter que l’omission de l’initialisation ‘univ = 0’ sur la première ligne aura pour effet de
diminuer la portée de la variable. Dans ce cas, chacune des procédure p1 et p2 aura sar propre variable locale univ, et l’appel de p2
aurait par là même produit une erreur :
undefined local variable or method `univ' for #<Object:0x153cd8> (NameError)
C’est une des caractéristique des objets procédure de pouvoir être passés en argument. Les variables partagées restent
ainsi valides même lorsqu’elles sont transmises hors de leur portée initiale.
def tiroir
contenu = 15
x = proc{contenu}
y = proc{|n| contenu = n}
return x, y
end
lecture, ecriture = tiroir
print lecture.call,"\n"
print ecriture.call(2),"\n"
print lecture.call,"\n"
Dans ce cas, la variable locale ‘univ’ est partagée par ‘main’ et par les objets procédure ‘p1’ et ‘p2’
[coruscant:~/ruby] chris% ruby loc.rb
15
2
2
[coruscant:~/ruby] chris%
Dans cet exemple, il est évident que la variable contenu est partagée entre lecture et ecriture. Il est toutefois possible de
fabriquer une multitude de paires lecture-ecritureen utilisant ‘tiroir’ tel qu’il est défini dans l’exemple, chaque paire partageant une
variable locale ‘contenu’ et les diverses paires n’interférant pas entre elles.
def tiroir
contenu = 15
x = proc{contenu}
y = proc{|n| contenu = n}
return x, y
end
lec_1, ecr_1 = tiroir
lec_2, ecr_2 = tiroir
print ecr_1.call(99),"\n"
print lec_1.call,"\n"
print lec_2.call,"\n"
Dans ce cas, la variable locale ‘univ’ est partagée par ‘main’ et par les objets procédure ‘p1’ et ‘p2’
[coruscant:~/ruby] chris% ruby loc.rb
99
99
15
[coruscant:~/ruby] chris%
Constantes de classe
Une constante doit avoir un nom qui commence par une majuscule.
Elle doir avoir au moins une affectation, mais peut sans problèmes être réaffectée plusieurs fois et même recevoir le résultat
d’un calcul.
L’implémentation actuelle de ruby génère un avertissement (mais pas une erreur) chaque fois qu’une nouvelle valeur est
affectée à une constante.
ruby> Constante = 10
10
ruby> Constante=20
(eval):1: warning: already initialized constant Constante
20
ruby> Constante = Constante ** 2
(eval):1: warning: already initialized constant Constante
400
ruby>
Le programme suivant fait la même chose.
Constante=10
print Constante,"\n"
Constante=20
print Constante,"\n"
ruby> Constante=20
Constante = Constante ** 2
print Constante,"\n"
Et les mêmes messages d’erreur apparaissent lors de son exécution.
[coruscant:~/ruby] chris% ruby cons.rb
10
cons.rb:3: warning: already initialized constant Constante
20
cons.rb:3: warning: already initialized constant Constante
400
[coruscant:~/ruby] chris%
Il est possible de définir des constantes à l’intérieur des classes, mais contrairement aux variables d’instance, elles seront
accessibles même en dehors de la classe.
class Constantes
C1=101
C2=102
C3=103
def impression
print "A l'interieur de la classe, appel par xx\n"
print C1," ",C2," ",C3,"\n"
end
end
print "A l'exterieur de la classe, appel par Constantes::xx\n"
print Constantes::C1," ",Constantes::C2," ",Constantes::C3,"\n"
Constantes.new.impression
Exécution :
[coruscant:~/ruby] chris% ruby cons.rb
A l'exterieur de la classe, appel par Constantes::xx
101 102 103
A l'interieur de la classe, appel par xx
101 102 103
[coruscant:~/ruby] chris%
Attention, une référence à une constante définie à l’intérieur d’une classe directement par son nom (xx), sans préciser la
classe (Classe ::xx) génère un message d’erreur de type :
cons.rb:13: uninitialized constant ConstClass (NameError)
Il est aussi possible de définir les constantes dans des modules.
module Constantes
C1=101
C2=102
C3=103
def impression
print "A l'interieur du module\n"
print C1," ",C2," ",C3,"\n"
end
end
include Constantes
print "A l'exterieur du module\n"
print C1," ",C2," ",C3,"\n"
impression
print "Tentative pour changer la valeur de C1.\n"
C1=345
print "C1 = ",C1,"\n"
print "Constantes::C1 = ",Constantes::C1,"\n"
On voit bien au moment de l’exécution qu’il existe deux constantes C1, l’une définie à l’intérieur du module Constantes à laquelle on
se réfère par Constantes ::C1 et l’autre définie dans main.
[coruscant:~/ruby] chris% ruby cons.rb
A l'exterieur du module
101 102 103
A l'interieur du module
101 102 103
Tentative pour changer la valeur de C1.
C1 = 345
Constantes::C1 = 101
[coruscant:~/ruby] chris%
Quelques remarques, tout d’abord, l’affectation de la valeur 345 à C1 créée une nouvelle constante locale et ne change en
aucun cas la valeur de constante ::C1 qui reste toujours accessible. Ensuite, une tentative de changer la valeur de C1 dans le
module (Constantes ::C1 = 548) génère automatiquement une erreur :
cons.rb:18: parse error
Constantes::C1 = 548
^
Traitement des exceptions: rescue
Tout programme peut, au moment de son exécution, être confronté à des problèmes inattendus (fichier à ouvrir inexistant,
disque saturé, erreur d’écriture, données non conformes…)
ruby> Mon_Fichier=open("donnees.txt")
ERR: (eval):1:in `open': No such file or directory - "donnees.txt"
ruby>
La gestion de ce type de situation est relativement éprouvante. Pour mémoire, rappelons comment un tel évènement serait
géré en C ou le résultat de tout appel système est supposé être vérifié et l’éventuelle erreur prise en compte :
#Ceci est du C…
#Pas du Ruby !
FILE *file = fopen("tel_fichier "r");
if (file == NULL) {
fprintf( stderr, "Ficher absent.\n" );
exit(1);
}
bytes_read = fread( buf, 1, bytes_desired, file );
if (bytes_read != bytes_desired ) {
/* ici, un traitement d'erreur ... */
}
… Suite du programme …
Ce travail est particulièrement rébarbatif, et nombre de concepteurs deviennent imprudents et négligent la gestion de
certaines erreurs avec comme résultat un programme qui peut présenter des disfonctionnements sérieux.
Notons aussi que la gestion systématique de tous les évènements système qui pourraient survenir tend à rendre le
programme lourd et parfois même difficile à modifier, le code utile étant noyé dans une multitude de lignes destinées à gérer les
évènements inattendus.
Ruby, comme le fait aussi perl, propose de gérer les exceptions relatives à un bloc de manière compartimentée et ainsi faire
face efficacement aux éventuelles surprises.
Le bloc de code qui commence par le mot clé ‘begin’ sera exécuté jusqu’à ce que survienne une possible exception. Celle ci
provoque un transfert du contrôle vers un bloc spécifique destiné à la gérer. Ce bloc est celui qui suit le mot clé ‘rescue’ (secours). Si
aucun problème ne se présente lors de l’exécution du bloc, alors le code ‘rescue’ est ignoré.
Le code suivant liste et compte les lignes d’un fichier dont le nom est lu au clavier.
def lister_le_fichier(nom_de_fichier)
begin
print "On liste le fichier ",nom_de_fichier,"\n\n"
file = open(nom_de_fichier)
i=1
while ligne = file.gets
print ligne
i += 1
end
print "\nLe fichier comporte ",i," lignes\n"
file.close
#Traitement de l’erreur lors de l’ouverture du fichier.
rescue
print "Erreur dans l'acces au fichier ",nom_de_fichier,"\n"
print "Type de l'erreur : \n"
print " ",$!,"\n"
print "Emplacement de l'erreur : \n"
print " ",$@,"\n "
end
end
print "Quel est le nom du fichier a lister : "
nom_de_fichier = STDIN.gets
nom_de_fichier.chop!
lister_le_fichier nom_de_fichier
Lors d’une exécution normale, le fichier existe. Lors de son ouverture, aucune erreur n’est générée. Le bloc ‘rescue’ est donc
ignoré et le fichier concerné est correctement listé :
[coruscant:~/ruby] chris% ruby prob.rb
Quel est le nom du fichier a lister : donnees.txt
On liste le fichier donnees.txt
Ligne numero 1 du fichier.
Ligne numero 2 du fichier.
Ligne numero 3 du fichier.
Ligne numero 4 du fichier.
Ligne numero 5 du fichier.
Ligne numero 6 du fichier.
Le fichier comporte 7 lignes
[coruscant:~/ruby] chris%
S’il y a erreur dans l’introduction du nom, alors le bloc ‘rescue’ est exécuté, un message d’erreur est imprimé et grace aux
variables standard prédéfinies, $ ! (type de l’erreur) et $@ (emplacement de l’erreur) on dispose des informations nécessaires pour
comprendre ce qui s’est passé.
[coruscant:~/ruby] chris% ruby prob.rb
Quel est le nom du fichier a lister : donnees.rb
On liste le fichier donnees.rb
Erreur dans l'acces au fichier donnees.rb
Type de l'erreur :
No such file or directory - "donnees.rb"
Emplacement de l'erreur :
prob.rb:4:in `open'prob.rb:4:in `lister_le_fichier'prob.rb:23
[coruscant:~/ruby] chris%
Une petite astuce consisterait a contourner le problème de manière constructive. Par exemple si le fichier est indisponible,
on bascule sur l’entrée standard.
Ceci permet par ailleurs lors de la mise au point du programme d’utiliser l’entrée standard, il suffit pour ce faire que le fichier
référencé soit inexistant. Le simple fait de le créer en permettra alors l’accès sans que le programme ne nécessite la moindre
modification.
def ouvrir_le_fichier(nom_de_fichier)
begin
file = open(nom_de_fichier)
print "On liste le fichier ",nom_de_fichier,"\n\n"
rescue
file = STDIN
print "Le fichier ",nom_de_fichier," n'existe pas, on bascule sur l'entree standard.\n\n"
end
i=1
while ligne = file.gets
print "-> ",ligne
i += 1
end
print "\nLe fichier comporte ",i," lignes\n"
file.close
end
print "Quel est le nom du fichier a lister : "
nom_de_fichier = STDIN.gets
nom_de_fichier.chop!
ouvrir_le_fichier nom_de_fichier
On voit lors de la première exécution un déroulement normal, le fichier concerné ayant été trouvé. La seconde exécution réfère à un
fichier inexistant, le bloc ‘rescue’ bascule alors sur l’entrée standard sur laquelle seront lues les données.
[coruscant:~/ruby] chris% ruby prob.rb
Quel est le nom du fichier a lister : donnees.txt
On liste le fichier donnees.txt
->
->
->
->
->
->
Ligne numero 1 du fichier.
Ligne numero 2 du fichier.
Ligne numero 3 du fichier.
Ligne numero 4 du fichier.
Ligne numero 5 du fichier.
Ligne numero 6 du fichier.
Le fichier comporte 7 lignes
[coruscant:~/ruby] chris% ruby prob.rb
Quel est le nom du fichier a lister : donnees.rb
Le fichier donnees.rb n'existe pas, on bascule sur l'entree standard.
ligne numero 1
-> ligne numero 1
ligne numero 2
-> ligne numero 2
ligne numero 3
-> ligne numero 3
^D
Le fichier comporte 4 lignes
[coruscant:~/ruby] chris%
Il existe une instruction ‘retry’ qui pourrait éventuellement être utilisée dans le bloc ‘rescue’ pour faire une nouvelle tentative.
Le danger vient du fait que si l’erreur est permane,te, le programme va indéfiniment boucler sur les ordres ‘rescue’ et ‘retry’.
Les librairies de ruby génèrent des exceptions si apparaît une erreur. Il est par ailleurs possible de générer soi même un tel
événement à l’intérieur de son propre code au moyen de l’instruction ‘raise’. Il est souhaitable d’utiliser un argument (une chaîne de
caractères) décrivant l’exception lors de l’utilisation de cette instruction. Cet argument étant optionnel, il peut être omis. Dans ce cas,
il est plus difficile de caractériser l’origine du ‘raise’. On a accès à cet argument par l’intermédiaire de la variable prédéfinie $ !.
begin
print "Programme generant un evenement.\n"
raise "Generation d'un evenement."
rescue
print "Contenu de la variable $! : ",$!, "\n"
end
Exécution :
[coruscant:~/ruby] chris% ruby event.rb
Programme generant un evenement.
Contenu de la variable $! : Generation d'un evenement.
[coruscant:~/ruby] chris%
Traitement des exceptions: ensure
Une méthode qui se termine ne laisse pas obligatoirement l’environnement de travail dans un état de référence. Il reste
possible que certains fichiers doivent être refermé, que certaines données en attende doivent être transmises etc…
Dans le cas ou la sortie de la méthode se fait en un point et en un seul, le code de remise en état se place à cet endroit et on
peut lui faire alors confiance pour que tout soit correctement achevé. Le problème est par contre posé lorsqu’une méthode donnée
possède plusieurs points de sortie. Une possibilité consiste alors à dupliquer le code pour le placer en chacun des emplacements de
sortie possible, ce qui nous met à l’abri des fins normales mais pas d’une sortie sur exception.
Une première solution consiste à générer un ‘rescue’ lors d’une tentative infructueuse d’ouverture, de l’utiliser pour fermer le
fichier puis d’exécuter un ‘retry’. C’est une méthode peu agréable.
On dispose pour achever une méthode d’une bloc spécifique, le bloc ‘ensure’ qui sera exécuté lorsque la méthode se
termine, et ce, quelle qu’en soit la manière.
def sorties(cond)
begin
case cond
when 0
raise "entree dans le raise."
when 1
print "Sortie normale\n"
end
rescue
print $!,"\n"
ensure
print "Passage dans ensure\n"
end
end
print "\nPremiere execution sans passer par l'exception :\n"
sorties(0)
print "\nSeconde execution en passant par l'exception :\n"
sorties(1)
Résultat :
[coruscant:~/ruby] chris% ruby event.rb
Premiere execution sans passer par l'exception :
entree dans le raise.
Passage dans ensure
Seconde execution en passant par l'exception :
Sortie normale
Passage dans ensure
[coruscant:~/ruby] chris%
Les deux blocs «’rescue’ et ‘ensure’ ne sont pas liés, il est possible d’en utiliser un sans être contraint d’utiliser l’autre.
Toutefois, lorsqu’ins sont utilisés dans le même bloc le ‘rescue’ doit impérativement précéder le ‘ensure’.
Accesseurs
Lorsqu’il a été question des variables d’instance, leur existence a été évoquée mais rien de précis n’a été dit à leur sujet.
Nous allons ici en développer l’utilisation et en montrer l’importance.
Les variables d’instance d’un objet sont les attributs qui vont le différencier des autres objets de la même classe.
Il est fondamental de pouvoir accéder a ces données qui permettront de caractériser de manière précise des objets par
ailleurs identiques.
Pour ce faire, on dispose de méthodes appelées accesseurs d’attributs les ‘writers’ et les ‘readers’. Comme leur nom
l’indique, les ‘writers’ vont nous permettre d’affecter des valeurs à ces variables alors due les ‘readers’ vont permettre d’en récupérer
le contenu.
class Animal
# Ecriveur
def set_famille(k)
@famille = k
end
# Lecteur
def get_famille
@famille
end
end
anim = Animal.new
print "Utilisation de l'ecriveur\n"
anim.set_famille("reptile")
print "Utilisation du lecteur\n"
assess = anim.get_famille
print assess,"\n"
Exécution :
[coruscant:~/ruby] chris% ruby assess.rb
Utilisation de l'ecriveur
Utilisation du lecteur
reptile
[coruscant:~/ruby] chris%
Il est facile de voir que nous manipulons ici l’information sur l’animal considéré.
Nous aurions pu faire exactement la même chose de manière plus concise sous la forme :
class Animal
# Ecriveur
def famille=(k)
@famille = k
end
# Lecteur
def famille
@famille
end
end
anim = Animal.new
print "Utilisation de l'ecriveur\n"
anim.famille="reptile"
print "Utilisation du lecteur\n"
assess = anim.famille
print assess,"\n"
Exécution :
[coruscant:~/ruby] chris% ruby assess.rb
Utilisation de l'ecriveur
Utilisation du lecteur
reptile
[coruscant:~/ruby] chris%
Reprenons l’exemple que nous venons de voir, et demandons maintenant d’imprimer l’objet anim.
class Animal
# Ecriveur
def famille=(k)
@famille = k
end
# Lecteur
def famille
@famille
end
end
anim = Animal.new
print "Impression de l'objet anim : ",anim,"\n"
Le résultat est le suivant :
[coruscant:~/ruby] chris% ruby assess.rb
Impression de l'objet anim : #<Animal:0x14771c>
[coruscant:~/ruby] chris%
Ce que nous obtenons #<Animal:0x14771c> n’est pas très explicite. Il s’agit là du comportement par défaut qu’il est possible
de modifier. Il suffit pour ce faire d’utiliser une méthode spécifique ‘inspect’ qui retournera une chaine de caractères qui nous
permettra de décrire l’objet sous la forme qui nous convient le mieux. Ainsi, il est par exemple possible de lister les variables
d’instance.
class Animal
# Ecriveur
def famille=(k)
@famille= k
end
# Lecteur
def famille
@famille
end
def inspect
"Nous traitons un animal qui est un "+@famille
end
end
anim = Animal.new
print "Utilisation de l'ecriveur\n"
anim.famille="reptile"
print "Utilisation du lecteur\n"
assess = anim.famille
print assess,"\n"
print "Impression de l'objet anim : \n"
p anim
Exécution :
[coruscant:~/ruby] chris% ruby assess.rb
Utilisation de l'ecriveur
Utilisation du lecteur
reptile
Impression de l'objet anim :
Nous traitons un animal qui est un reptile
[coruscant:~/ruby] chris%
Nous avons ici introduit la méthode p. Cette dernière permet d’obtenisr facilement des affichages permettant une mise au
point rapide des programmes.
Dans l’exemple précédent, il aurait été équivalent d’écrire ‘p anim’ et ‘print « anim.inspect, »\n”’.
Comme il est nécessaire d’avoir sans arrêt besoin d’assesseurs, ruby met à disposition des raccourcis plus faciles à utiliser
que les formes standard.
Raccourci
attr_reader :v
attr_writer :v
attr_accessor :v
attr_accessor :v, :w
Forme équivalente
def v; @v; end
def v=(value); @v=value; end
attr_reader :v; attr_writer :v
attr_accessor :v; attr_accessor :w
Ainsi donc, nous pouvons réécrire le programme précédent sous la forme plus concise :
class Animal
attr_accessor :type
attr_writer :famille
attr_reader :famille
def inspect
"Nous traitons un animal qui est un "+@famille+" de type "+@type
end
end
anim = Animal.new
print "Utilisation de l'ecriveur\n"
anim.famille="reptile"
anim.type = "lezard"
print "Utilisation du lecteur\n"
assess = anim.famille
print assess,"\n"
print "Impression de l'objet anim : \n"
p anim
Pour un même résultat :
[coruscant:~/ruby] chris% ruby assess.rb
Utilisation de l'ecriveur
Utilisation du lecteur
reptile
Impression de l'objet anim :
Nous traitons un animal qui est un reptile de type lezard
[coruscant:~/ruby] chris%
Regroupons l’ensemble de nos routines, nous mettons ainsi plusieurs propriétés en evidence et rajoutons à la fin une
déclaration d’un nouvel animal , un quadrupède.
class Animal
attr_accessor :etat
def famille=(k)
@famille = k
end
def famille
@famille
end
def inspect
"Animal de type " + @famille + " a l'etat "+@etat
end
def plus_tard
@etat = "poussin"
end
end
quadrupede = Animal.new
p quadrupede
L’exécution fait apparaître une série de messages d’erreur :
[coruscant:~/ruby] chris% ruby assess.rb
assess.rb:10:in `+': failed to convert nil into String (TypeError)
from assess.rb:10:in `inspect'
from assess.rb:18:in `p'
from assess.rb:18
[coruscant:~/ruby] chris%
C’est en créant la méthode inspect que nous avons créé un petit problème.
Il faut se souvenir que les variables d’instance n’existent pas tant qu’une valeur ne leur a pas été explicitement assignée.
Ceci a déjà été précisé.
Lors de la création de l’objet quadrupède, la méthode ‘inspect’ se plaint du fait qu’on lui demande de rendre compte du type
(@famille) et de l’état (@etat) qui, dans le cas du quadrupède n’ont reçu aucune valeur explicite.
Pour ne pas avoir de problème, il faudrait réécrire inspect en lui demandant au préalable de tester au moyen de la méthode
‘defined ?’ l’état des variables d’instance et de n’y faire référence que si elles ont recu une valeur.
Il existe une autre solution plus pratique qui consiste a procéder à une initialisation des objets.
Initialisation des objets
Nous avions défini notre classe Animal en lui donnant deux variables d’instance, l’une spécifiant sa famille, l’autre son état.
La méthode ‘inspect’ n’avait écrite qu’après. Cette méthode faisant référence à ces deux attributs nous contraignait à les initialiser,
chose qui n’était pas faite lors de la création d’un nouvel objet. D’ou, l’erreur lors de la déclaration de l’objet quadrupède.
Ruby nous fournit un moyen simple d’initialiser les variables d’instance, il s’agit de la méthode ‘initialize’.
Chaque fois qu’est créé un nouvel objet, si une méthode ‘initialise’ est presente dans sa définition, alors les valeurs par
défaut qui sont indiquées dans cette méthode seront automatiquement affectées à toutes les variables d’instance listées.
Il suffit donc de rajouter cette nouvelle méthode pour résoudre le problème.
class Animal
attr_accessor :etat
def famille=(k)
@famille = k
end
def famille
@famille
end
def inspect
"Animal de type " + @famille + " a l'etat "+@etat
end
def plus_tard
@etat = "poussin"
end
def initialize
@famille="Generique"
@etat="Vivant"
end
end
quadrupede = Animal.new
p quadrupede
Exécution :
[coruscant:~/ruby] chris% ruby assess.rb
Animal de type Generique a l'etat Vivant
[coruscant:~/ruby] chris%
Ceci étant, définir à priori un animal vivant de type générique ne présenta pas réellement un avantage.
Nous pouvons alors spécifier au moment de la déclaration de l’oblet quelle valeur sera affectée à ses variables d’instance.
Cette opération peut de réaliser en passant une liste de paramètres au moment de la création de l’objet.
La méthode ‘initialise’ demande maintenant à disposer des deux paramètres fam et etat qui permettront de procéder à une
initialisation de ces deux variables @famille et @etat.
class Animal
attr_accessor :etat
def famille=(k)
@famille = k
end
def famille
@famille
end
def inspect
"Animal de type " + @famille + " a l'etat "+@etat
end
def plus_tard
@etat = "poussin"
end
def initialize (fam, etat)
@famille=fam
@etat=etat
end
end
quadrupede = Animal.new "Mamifere","Adulte"
p quadrupede
poisson=Animal.new
p poisson
Un nouveau problème apparaît alors, La méthode ‘initialise qui a été définie pour recevoir deux paramètres, exige maintenant d’avoir
ses deux paramètres et génère un message d’erreur si elle ne les a pas.
[coruscant:~/ruby] chris% ruby assess.rb
Animal de type Mamifere a l'etat Adulte
assess.rb:22:in `initialize': wrong # of arguments(0 for 2) (ArgumentError)
from assess.rb:22:in `new'
from assess.rb:22
[coruscant:~/ruby] chris%
Une fois qu’une liste d’arguments a été associée à une méthode, catta liste ne peut plus être omise sans générer une erreur.
Le message qui apparaît lors de la déclaration de l’objet poisson indique bien que la méthode s’attend a trouver une liste de
deux arguments et qu’elle n’en a trouvé aucun.
Nous devons donc disposer d’un moyen pour faire en sorte que l’argument soit utilisé si il est présent et qu’une valeur par
défaut soit utilisée dans le cas contraire.
class Animal
attr_accessor :etat
def famille=(k)
@famille = k
end
def famille
@famille
end
def inspect
"Animal de type " + @famille + " a l'etat "+@etat
end
def plus_tard
@etat = "poussin"
end
def initialize (fam="Generique", etat="Vivant")
@famille=fam
@etat=etat
end
end
quadrupede = Animal.new "Mamifere","Adulte"
p quadrupede
poisson=Animal.new
p poisson
Exécution :
[coruscant:~/ruby] chris% ruby assess.rb
Animal de type Mamifere a l'etat Adulte
Animal de type Generique a l'etat Vivant
[coruscant:~/ruby] chris%
A noter que cette notion d’arguments pas défaut peut être utilisée pour toutes les méthodes à laquelle est passée une liste.
La liste doit alors être ordonnée afin que ceux qui possèdent des valeurs par défaut apparaissent en dernier.
Divers problèmes.
Délimiteurs d'instruction.
Ruby suit la convention des shells unix comme sh et csh. Si il existe une sé&rie d’instructions sur une même ligne, elles
doivent être séparées par des point-virgules. Il n’est pas indispensable de faire apparaître ce séparateur sur une ligne qui ne
contient qu’une instruction, la fin de ligne étant traitée comme un point-virgule.
Toute ligne qui se termine sur un antislash (\), se continue sur la ligne suivante, il est ainsi possible de disposer d’une unique
ligne logique étalée sur plusieurs lignes physiques.
Organisation du code.
Le code est traité au fur et à mesure qu’il se présente. Il n'y a pas de compilation. Ce qui n'a pas été lu est indéfini.
print successor(3),"\n"
def successor(x)
x+1
end
L’exécution fait apparaître l’erreur précisant que la méthode ‘successor’ n’est pas connue au moment ou elle est appelée.
[coruscant:~/ruby] chris% ruby divers.rb
divers.rb:1: undefined method `successor' for #<Object:0x153cd8> (NameError)
[coruscant:~/ruby] chris%
Il suffit de déplacer la requète après l’a définition de la méthode pour que tout rentre dans l’ordre.
def successor(x)
x+1
end
print successor(3),"\n"
Et que l’exécution ne génère plus de message d’erreur, la méthode ‘successor’ etant connue au moment ou elle est appelée.
[coruscant:~/ruby] chris% ruby divers.rb
4
[coruscant:~/ruby] chris%
Toutefois, une méthode peut contenir des références non résolues dans sa définition à condition qu’elles le soient au
moment ou elle sera effectivement utilisée.
# Calcul de la somme des n premiers entiers.
def somme(n)
(n*succ(n))/2
end
def succ(x)
x+1
end
i=5
print "La somme des ",i," premiers entiers est egale a : ",somme(i),"\n"
Exécution :
[coruscant:~/ruby] chris% ruby divers.rb
La somme des 5 premiers entiers est egale a : 15
[coruscant:~/ruby] chris%
Dans cet exemple, au moment de la définition de somme, la méthode succ n’est pas connue. Mais elle est definie avant
l’appel de somme.
Et en guise de conclusion.
Pour toute information supplémentaire sur ruby, il est possible de se reporter aux sites consacrés au langage.
Voici les deux principaux :
http://www.rubycentral.com/
http://www.ruby-lang.org/en/
Vous y trouverez las manuels de référence, la foire aux questions et la librairie de référence.

Documents pareils