Inférence de type en Eiffel

L’inférence de type est un mécanisme permettant au compilateur de déduire automatiquement le type d’une cible en se basant sur le type de la source.
L’utilisateur n’a alors plus besoin de déclarer ses variables et donc de se soucier du nom du type qui rentre en jeu. Le programme obtenu est plus générique, plus à même aux changements. En effet si le type de la source change, le type de la cible est automatiquement modifié.
L’inférence de type apporte une plus grande souplesse au typage statique.

Typage statique

Exceptés deux cas (nous en verrons un par la suite), une variable ou un attribut en Eiffel doit toujours être déclaré.
Ci-dessous est définie une classe LINE avec une procédure de création (constructeur) from_complexs et deux attributs first et second de type POINT.
La procédure de création prend deux paramètres de type COMPLEX et crée les deux attributs.


class
	LINE

create
	from_complexs

feature -- Initialization

	from_complexs (a, b: COMPLEX)
		do
			create first.from_complex (a)
			create second.from_complex (b)
		end

feature -- Access
	
	origin: POINT
		once
			create Result
		end

	first, second: POINT

end

Typage ancré

Le typage ancré a été introduit dans Eiffel pour apporter une plus grande flexibilité au typage statique et pour permettre la covariance dont nous ne parlerons pas dans cet article.
Il permet de spécifier que le type d’une cible est identique au type d’une source.


origin: POINT
	once
		create Result
	end

first, second: like origin

Les attributs first et second sont du même type que la fonction (à exécution unique) origin.

Le typage ancré permet d’écrire un programme plus générique et évolutif. Cependant, la déclaration des variables est toujours nécessaire.

Eiffel void safe

Nous souhaiterions donner à l’utilisateur la possibilité de spécifier un paramètre comme vide (nul).
Une variable, ou un attribut pouvant être affecté de la valeur vide doit être déclaré comme « detachable ».
La variable étant potentiellement vide, il est nécessaire de faire un test d’attachement avant de lui appliquer des opérations.


from_complexs (a: COMPLEX; b: detachable COMPLEX)
	do
		create first.from_complex (a)
		if b /= Void then
		      create second.from_complex (b)
		else
		      create second.from_complex (origin)
		end
	end

b /= Void peut être remplacé par attached b.
b est en lecture seule à l’intérieur de la condition.

En revanche aucune opération n’est applicable sur un attribut détachable, car sa valeur peut être modifiée lors de l’appel d’autres procédures. Il est donc nécessaire d’avoir un mécanisme qui fasse à la fois un test d’attachement et une affectation à une variable locale. Le patron d’attachement est ce mécanisme. Il peut bien entendu être utilisé avec des variables locales comme illustrées ci-dessous.


from_complexs (a: COMPLEX; b: detachable COMPLEX)
	do
		create first.from_complex (a)
		if attached b as c then
		      create second.from_complex (c)
		else
		      create second.from_complex (origin)
		end
	end

Si b est attaché à un objet alors il est affecté à la variable locale c. La variable nouvellement créée est en lecture seule comme le sont les paramètres formels ou les constantes.
Le mécanisme introduit par la même occasion une inférence de type.

Proposition d’une inférence de type

Une première approche pourrait être de déduire le type d’une variable lors de sa première affectation.


new_target := source

Mais cette approche à plusieurs inconvénients :
— Le compilateur ne peut pas détecter les erreurs de frappes. C’est une erreur récurrente et qui n’est pas si facile à identifier sans le compilateur.


new_target := source
mew_target := source2

— Nous pouvons par erreur utiliser le nom d’un attribut de notre classe. Le compilateur n’a aucun moyen d’identifier cette erreur.
— La sémantique d’une affectation devient plus complexe. En effet, elle peut conduire à la création d’une nouvelle variable.

Nous avons besoin d’un mécanisme distinct d’une affectation qui provoque la création d’une nouvelle variable.

En faisant une petite recherche sur les moteurs de recherche, on peut trouver une proposition qui va dans ce sens.


new_target ::= source

Cependant la syntaxe utilisée est nouvelle et se démarque peu d’une affectation.

En nous inspirant du patron d’attachement, nous pouvons créer un mécanisme simple.


source as new_target

Quel est le type de la cible ?
La cible est du type de la source. Lorsque la source est détachable, le mécanisme a peu d’intérêt. Dans ce cas, il est préférable d’utiliser le patron d’attachement. Nous pouvons donc réserver ce mécanisme aux sources de type attaché.

La variable nouvellement créée doit-elle être en lecture seule ?
Le mot clé « as » suggère une liaison plus forte entre la cible et la source qu’avec une affectation.
Les variables en lecture seule garantissent l’accès à un même objet tout au long de la routine, évitant ainsi une erreur d’interprétation.
Je pense qu’il est donc judicieux d’avoir la variable nouvellement créée en lecture seule.

Ce choix peut toujours être révisé dans une version ultérieure. En effet, l’introduction de la mutabilité d’une variable inférée sera rétrocompatible, l’inverse ne l’est pas.
Ce choix est également motivé par la volonté d’avoir un maximum de similitudes sémantiques avec le patron d’attachement.


if attached (source as new_target) then
	new_target.do_something -- compilation error
end

la compatibilité est atteinte en considérant que source as new_target est une expression qui donne comme résultat la variable nouvellement créée.
Nous pouvons remarquer un changement sémantique qui n’a aucune conséquence effective. En effet lorsque la source est détachable, le type de la cible n’est plus attaché, mais détachable.

Cependant, la distinction nette entre expression et déclaration en Eiffel pousse à garder une séparation entre le mécanisme proposé ici et le patron d’attachement.

Résumons :
1. Le type de la source doit être attaché.
2. Le nom de la cible ne doit pas identifier une autre variable.
3. Le type de la cible est identique à celui de la source.
4. La cible est en lecture seule.
5. Le mécanisme provoque la création d’une nouvelle variable et son initialisation.
6. Le mécanisme n’est pas une expression, mais une déclaration.