Historique
-
- Créé par Bertrand Meyer;
- Première version en 1986;
- Les trois vies du langage Eiffel:
- Langage théorique
- Écriture du livre « Object-Oriented Software Construction »;
- Aucun langage à l’époque ne permettait de représenter les différents concepts du livre;
- Le langage a été créé afin de représenter les exemples du livre;
- Aucun compilateur ou interpréteur n’était disponible.
- Langage pédagogique
- Le livre a été beaucoup utilisé dans un contexte pédagogique;
- Afin que les étudiants et les enseignants puissent avoir une meilleure expérience avec le livre, un compilateur Eiffel minimaliste a été créé.
- Langage de production
- L’engouement pour un réel compilateur Eiffel a encouragé son créateur à fonder une compagnie (International Software Engineering) afin de développer un compilateur commercial;
- La première version du standard officiel (ECMA-367) a été créée en 2005.
- Langage théorique
- Malgré que le langage n’est pas beaucoup utilisé dans l’industrie, il a influencé une grande quantité de langage moderne.
- Par exemple, les langages suivants se sont inspirés de Eiffel: Java, C#, Scala, Ruby, etc.
- Encore aujourd’hui, les langages ajoutent des technologies développées initialement dans Eiffel:
- Par exemple:
- Java et C# ont intégré l’approche par contrat;
- Java a inclue une fonctionnalité de fonction pure dans les dernières versions;
- Java, C# et Kotlin se sont inspirés du Void-Safety afin de créer leur technologie « null safe »;
- etc.
- Par exemple:
Le langage Eiffel
Les principaux objectifs du langage Eiffel sont les suivants:
-
- Langage orienté objet pur
- Tout est un objet;
- Aucun type primitif;
- Aucune fonction « main »;
- Concept de classe abstraite et classe effective;
- Une seule structure d’héritage (pas d’interface);
- etc.
- Langage très performant:
- Il s’agit d’un langage compilé ayant pour cible la machine de l’utilisateur:
- Aucun interpréteur (comme Python, JavaScript, etc.);
- Aucune machine virtuelle (comme Java ou C#);
- Performance similaire aux langages C et C++;
- Il s’agit d’un langage compilé ayant pour cible la machine de l’utilisateur:
- Réutilisation des composantes:
- Limiter le moins possible l’héritage;
- Aucune notion de méthode ou attribut privé;
- Favoriser au maximum la robustesse des logiciels développés:
- « Void-Safety »;
- « Desing » par contrat;
- Mécanismes de masquage de l’information très avancés;
- etc.
- Langage orienté objet pur
Pourquoi Eiffel
Considérant le fait que le langage Eiffel n’est pas beaucoup utilisé dans l’industrie, il est légitime de se demander pourquoi apprendre ce langage au lieu d’un autre. Voici certaines pistes de réflexion qui vous permettra de répondre à ce questionnement.
-
- Apprendre des langages de programmation différents vous permettra d’être plus flexible en tant que programmeur;
- Puisque les structures objets d’Eiffel n’ont aucune limites imposées, Eiffel est le langage le plus efficace pour apprendre les concepts de la programmation orientée objet:
- Pas de double structure d’héritage (pas d’interface, seulement des classes);
- Héritage très structuré;
- Tout est un objet (incluant les entiers, les caractères, etc.)
- etc.
- Concepts originaux qui vous permettront de développer une meilleure rigueur de programmation:
- « Void-Safety »,
- « Desing » par contrat,
- etc.
- Eiffel est un langage puissant, précis, formel et élégant qui vous permettra de créer de meilleur programme en général.
Une classe en Eiffel
La meilleure façon de comprendre le code Eiffel est de le voir séparé en section. Voici un exemple:
Les clauses « feature »
L’accolade après la clause « feature » correspond à une liste des classes à avoir accès aux méthodes et attributs contenue dans cette section. Généralement, si aucune classe n’est spécifiée, les méthodes et attributs sont considérés comme publics et si on y place « {NONE} », ça indique que les méthodes et attributs sont considérés comme privé (au sens de Eiffel: les descendants ont accès à ces méthodes et attributs).
Les sections « feature » peuvent varier. Par exemple, il n’est pas nécessaire de mettre de « feature » libellée « constantes » et cette clause « feature » aurait pu être mise publique au lieu de privé.
En général, on place minimalement une section « feature {NONE} –Initialisation » pour les constructeurs, une section « feature –Accès » pour l’interface de la classe (les méthodes et attributs publics) et une section « feature {NONE} –Implémentation » pour les méthodes et attributs privés. Par contre, rien ne vous empêche d’être plus précis et de faire des sections plus particulières.
Les méthodes
La base d’une méthode s’écrit de la manière suivante:
valider_point_de_vie
-- S'assure que les `points_de_vie' soient entre
-- 0 et `Points_de_vie_maximaux'.
do
if points_de_vie < 0 then
points_de_vie := 0
elseif points_de_vie > Points_de_vie_maximaux then
points_de_vie := Points_de_vie_maximaux
end
end
La première ligne correspond à la signature de la méthode. Si aucun type de retour n’est nécessaire (la méthode est une procédure), on ne spécifie rien. Si aucun argument n’est nécessaire, nous n’avons pas à y placer les parenthèses vides « () ».
Ensuite, nous avons la documentation de la méthode sous forme de commentaires. Une bonne documentation Eiffel doit être concise et précise et faire référence aux arguments, autres méthodes et attributs auxquels il a un lien. Les références aux arguments, méthodes et attributs se font en mettant ces derniers entre accents graves « ` » et apostrophe « ‘ ». Si une référence à une classe est utilisée, mettre cette classe en majuscule, entre accolades « {CLASSE} ».
Enfin, on voit que le corps du texte se trouve entre le « do » et le « end ».
Voici un autre exemple plus complet:
calculer_moyenne(a_nombres:LIST[INTEGER]):INTEGER
-- Retourne la moyenne des nombres de `a_nombres'
local
l_somme:INTEGER
do
l_somme := 0
across a_nombres as la_nombres loop
l_somme := l_somme + la_nombres.item
end
Result := l_somme // a_nombres.count
end
On voit que les arguments sont placés entre parenthèses « () » et le type de retour est spécifié à l’aide d’un deux-points « : » à la fin de la signature.
Ensuite, en Eiffel, toutes les variables locales de la méthode doivent être déclarées dans une clause « local ». De cette manière, on peut savoir rapidement toutes les variables globales qui seront utilisées dans la méthode.
Enfin, puisque Eiffel renforce le principe d’un seul point d’entrée et d’un seul point de sortie de toutes structures de contrôle, il n’y a pas de « return » en Eiffel. Pour retourner un résultat précis, il suffit d’assigner la variable spéciale « Result » avec la valeur que l’on veut retourner. D’ailleurs, il faut voir la variable « Result » comme une variable locale standard. On peut l’utiliser n’importe où dans la méthode. Par exemple:
calculer_somme(a_nombres:LIST[INTEGER]):INTEGER
-- Retourne la somme des nombres de `a_nombres'
do
Result := 0
across a_nombres as la_nombres loop
Result := Result + la_nombres.item
end
end
La méthode retournera la somme totale de tous les nombres puisque les nombres sont directement additionnés dans la variable « Result ». Ainsi, la fin de l’exécution de la méthode, la variable « Result » contient la somme totale.
Les conditionnelles « if »
Les conditionnelles « if » sont très similaires à la manière dont on les utilise dans les langages comme Java. Voici un exemple pour voir la syntaxe à utiliser:
make
-- Exécution de l'application
local
l_nom:STRING
l_age:INTEGER
do
io.put_string ("Quel est votre nom: ")
io.read_line
l_nom := io.last_string
io.put_string ("Quel est votre age: ")
io.read_integer_32
l_age := io.last_integer_32
if l_age > 30 then
io.put_string ("Bonjour " + l_nom + "%N")
elseif l_age > 18 then
io.put_string ("Salut " + l_nom + "%N")
else
io.put_string ("Yo " + l_nom + "%N")
end
end
Les boucles
En Eiffel, il existe 2 types de boucles: les boucles « until » et les boucles « across ».
Les boucles « until »
La boucle « until » peut servir à tout type de boucle. Il s’agit d’une certaine manière de l’équivalent d’une boucle « while » dans les langages basés sur le C (C++, Java, C#, etc.)
La différence entre les boucles « while » de C et les boucles « until » de Eiffel est que la condition est inversée. Dans le cas de la boucle « while », la condition indique dans quel cas le programme doit rester dans la boucle (« reste dans la boucle tant que la condition est vraie ») tandis que dans la boucle « until » de Eiffel, la condition indique dans quel cas le programme doit sortir de la boucle (« sort de la boucle lorsque la condition est vraie »).
Voici la syntaxe d’une boucle « until »:
from i := 1 until i > 10 loop
-- corps de la boucle
i := i + 1
end
La clause « from » consiste en l’initialisation des variables qui seront utilisées dans la condition de sortie de la boucle « until ». Il est possible de mettre plusieurs instructions dans cette clause. Le contenu de cette clause peut être vide.
La clause « until » consiste en la condition de sortie de la boucle. Cette clause est obligatoire et doit contenir une condition booléenne.
La clause « loop » contient le corps de la boucle (les instructions qui seront utilisées dans la boucle).
À noter qu’on voit souvent une version multiligne de cette structure. C’est très pratique lorsque l’initialisation et la condition sont assez longues et plus complexes. Par exemple:
from
liste.start
trouve := False
until
liste.exhausted or
trouve
loop
if liste.item ~ valeur then
trouve := True
end
liste.forth
end
Les boucles « across »
Les boucles « across » servent principalement à parcourir une structure de donnée itérable. Voici la syntaxe:
across liste as la_liste loop
-- Corps de la boucle
-- L'objet en cours peut être utilisé avec la_liste.item
end
Voici un exemple complet:
trouve := False
across liste as la_liste loop
if la_liste.item ~ valeur then
trouve := True
end
end
Le « Void-Safety »
Le « Void-Safety » est une technologie développée très récemment dans le langage Eiffel, qui garantit, à la compilation, qu’aucun déréférencement de pointeur NULL (appelé Void en Eiffel) ne sera effectué par le programme, lors de l’exécution.
Dans le domaine de la programmation, le déréférencement de pointeur NULL est un des « bugs » les plus importants. Tony Hoare, le créateur du langage ALGOL W et le créateur du concept de pointeur NULL considère cette création comme son erreur de 1 milliard de dollars (Voir: https://en.wikipedia.org/wiki/Null_pointer).
Par exemple, prenons le code Java suivant:
import java.io.File;
public class Test {
public static void main(String[] arguments) {
String texte = null;
System.out.println(texte.toLowerCase());
}
}
L’exécution de ce programme vous donnera une exception de type « NullPointerException ». Il s’agit en fait d’un exemple minimaliste, mais les cas plus concrets peuvent être beaucoup plus complexes. Ils peuvent également être très difficiles à régler.
Dans les langages permettant l’utilisation de pointeur NULL, certains permettent de déceler certains problèmes potentiels de déréférencement de pointeur NULL; mais très peu ne garantit qu’il n’y en aura jamais. Eiffel est de ces derniers.
Afin de régler définitivement le problème de déréférencement de pointeur NULL, Eiffel force toute variable à être initialisée avant le possible déréférencement de cette variable. Cette dernière règle prévaut également pour l’initialisation des attributs par le constructeur. C’est à dire, si le constructeur peut se terminer sans que le compilateur puisse garantir que l’attribut est initialisé, le compilateur donnera une erreur de compilation afin de vous permettre de vous assurer que l’attribut est bel et bien initialisé dans tous les cas. Par exemple:
class
TEST
create
make
feature {NONE} -- Initialisation
make
-- Initialisation de `Current'
do
print("Initialisation de l'objet")
end
feature -- Accès
attribut1:STRING
end
Ce code donnera une erreur « VEVI: variable is not properly set ». Ça indique en fait que le compilateur ne peut pas confirmer que l’attribut (« attribut1 ») est correctement initialisé à la fin du constructeur « make ».
Voici un autre exemple plus complexe:
class
TEST
create
make
feature {NONE} -- Initialisation
make(a_textes:LIST[STRING])
-- Initialisation de `Current'
do
if a_textes.count > 0 then
create attribut1.make_empty
across a_textes as la_textes loop
attribut1 := attribut1 + a_textes.item
end
end
end
feature -- Accès
attribut1:STRING
end
Dans cet exemple, si la liste de textes reçue en argument est vide, l’attribut ne sera pas initialisé à la fin du constructeur. Nous obtenons donc encore une erreur de code VEVI.
Utilisation d’objet Void
Le fait qu’Eiffel est « Void Safe » n’indique pas qu’il est impossible d’utiliser des objets Void. Par contre, ces objets doivent être déclarés comme étant « detachable ». Par exemple: l’exemple suivant (qui ne compilait pas précédemment) compile maintenant sans problème:
class
TEST
create
make
feature {NONE} -- Initialisation
make
-- Initialisation de `Current'
do
print("Initialisation de l'objet")
end
feature -- Accès
attribut1:STRING
end
Par contre, si j’essaie d’utiliser l’attribut potentiellement non initialisé, j’obtiens de nouveau une erreur de type VEVI. Par exemple:
class
TEST
create
make
feature {NONE} -- Initialisation
make
-- Initialisation de `Current'
do
print("Initialisation de l'objet")
end
feature -- Accès
ajoute_texte(a_texte:STRING)
-- Ajoute `a_texte' à `attribut1'
do
attribut1 := attribut1 + a_texte
end
attribut1:detachable STRING
end
En effet, lors de l’utilisation de l’attribut1 dans la méthode « ajoute_texte », il est possible que l’attribut un ne soit pas initialisé. Donc, pour utiliser un attribut détachable, il faut faire un « if attached », comme ceci:
class
TEST
create
make
feature {NONE} -- Initialisation
make
-- Initialisation de `Current'
do
print("Initialisation de l'objet")
end
feature -- Accès
ajoute_texte(a_texte:STRING)
-- Ajoute `a_texte' à `attribut1'
do
if attached attribut1 as la_attribut1 then
attribut1 := la_attribut1 + a_texte
end
end
attribut1:detachable STRING
end
La variable « la_attribut1 » dans cet exemple est une copie du pointeur de l’attribut1. Cette copie est essentielle, car il n’est pas impossible qu’entre l’exécution du « if » et le déréférencement du pointeur, l’attribut1 soit remis à Void. Voici un exemple:
class
TEST
create
make
feature {NONE} -- Initialisation
make
-- Initialisation de `Current'
do
print("Initialisation de l'objet")
end
feature -- Accès
ajoute_texte(a_texte:STRING)
-- Ajoute `a_texte' à `attribut1'
do
if attached attribut1 then
retire_texte
attribut1 := attribut1 + a_texte
end
end
retire_texte
-- Retire l'objet `attribut1'
do
attribut1 :=void
end
attribut1:detachable STRING
end
Il est clair que dans cet exemple, même si le « if » vérifie que l’attribut1 n’est pas Void, lors de l’utilisation de l’attribut1 (dans l’expression « attribut1 + a_texte »), cet attribut sera Void puisque la méthode « retire_texte » a mis ce dernier à Void. Donc, le code suivant compilerait correctement puisque « la_attribut1 » est utilisé au lieu de attribut1 dans l’expression potentiellement problématique (« la_attribut1 + a_texte »):
class
TEST
create
make
feature {NONE} -- Initialisation
make
-- Initialisation de `Current'
do
print("Initialisation de l'objet")
end
feature -- Accès
ajoute_texte(a_texte:STRING)
-- Ajoute `a_texte' à `attribut1'
do
if attached attribut1 as la_attribut1 then
retire_texte
attribut1 := la_attribut1 + a_texte
end
end
retire_texte
-- Retire l'objet `attribut1'
do
attribut1 :=void
end
attribut1:detachable STRING
end
Auteur: Louis Marchand
Sauf pour les sections spécifiées autrement, ce travail est sous licence Creative Commons Attribution 4.0 International.