Le langage Eiffel

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.
    • 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.

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++;
    • 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.

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

Retour


Auteur: Louis Marchand
Creative Commons License
Sauf pour les sections spécifiées autrement, ce travail est sous licence Creative Commons Attribution 4.0 International.