Inférence de types, polymorphie et traits impératifs

publicité
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Programmation Fonctionnelle Avancée
7 : Inférence de types, polymorphie et traits
impératifs
Ralf Treinen
Université Paris Diderot
UFR Informatique
Institut de Recherche en Informatique Fondamentale
Rappel sur le typage en OCaml
Les deux traits essentiels du système de typage de OCaml sont :
I
I
Système de types polymorphe : List .map manipule des listes
de tout type. Les listes sont polymorphes, mais homogènes
(dans chaque liste, tous les éléments ont le même type).
Inférence de types : le système découvre tout seul le type le
plus général, sans besoin de déclarer les types des
identicateurs.
[email protected]
15 novembre 2016
c Roberto Di Cosmo et Ralf Treinen
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Exemples (inf1.ml)
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Exemples (inf2.ml)
L i s t . map ; ;
let k x y = x ; ;
L i s t . map ( fun x −> x + 1 ) [ 1 ; 2 ; 3 ] ; ;
let s x y z = (x y) (y z ) ; ;
L i s t . map ( fun s −> s ^"−" ) [ " a " ; "b" ; " c " ] ; ;
let f x y z = x (y z) (y , z ) ; ;
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Inférence de types
Système d'équations entre types
I
Comment est-ce que OCaml fait pour trouver le type le plus
général d'un identicateur ?
I
Regardons d'abord le dernier exemple du transparent
précédant.
I
Il s'agit d'un cas simple : pas de récurrence.
I
On introduit une variable pour le type de chaque identicateur
nouveau (ici : pour les identicateurs f , x, y , z ), et une variable
pour chacune des expressions du côté droite :
I
Rappel : → associe à droite, c.-à-d. :
x →y →z
I
x → (y → z)
Principe pour let f x1 ... xn = e :
I
I
e1
est à lire comme
I
tf = tx 1 → . . . → txn → te
si e = (e 1, e 2) : te = te 1 × te 2
si e = e 1 e 2 . . . en : te 1 = te 2 → . . . ten → te
z
}|
{
let f x y z = x (y z) (y , z)
| {z } | {z }
e2
I
e3
Variables : tf , tx , ty , tz , t1 , t2 , t3
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Le typage en OCaml
Systèmes d'éqyautions entre types
I
Système d'équations entre types
On a :
e1
z
}|
{
let f x y z = x (y z) (y , z)
| {z } | {z }
e2
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
e3
On écrit des équations, on utilisant les règles d'associativité :
I
Comment obtenir tf à partir de ces équations ?
I
On peut d'abord réécrire le système d'équations en une forme
plus habituelle :
tf
= f (tx , f (ty , f (tz , t1 )))
tf
=
tx → (ty → (tz → t1 ))
tx
= f (t2 , f (t3 , t1 ))
tx
=
t2 → (t3 → t1 )
ty
= f (tz , t2 )
ty
=
tz → t2
t3 = p(ty , tz )
t3
=
ty × tz
où f (x, y ) remplace x → y , et p(x, y ) remplace x × y .
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Le typage en OCaml
Le sens des équations entre types
I
I
Dans les équations il y a des variables, et des constructeurs de
types f (→) et p (×), et éventuellement des constantes (int,
bool).
Propriétés des constantes et constructeurs :
I
I
I
Deux termes avec des constructeurs/constantes diérentes à la
tête ne peuvent jamais être égaux.
p(x1 , x2 ) = p(y1 , y2 ) exactement si x1 = y1 et x2 = y2 . Pareil
pour f .
Ce sont précisément les lois des symboles de fonctions non
interprétées comme on les connaît de la Logique !
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Donné :
I
Resolution d'équations entre types
I
L'algorithme pour résoudre des équations entre termes dans
une structure de symboles de fonctions non interprétées est
précisément l'algorithme d'unication de Herbrand (voir un
cours de Logique) !
I
L'unication nous donne soit l'information que le système
d'équations n'a pas de solution, soit une solution la plus
générale (mgu) : toute solution peut être obtenu comme
instance du mgu.
I
Nous cherchons le type le plus général de f qui est permis par
la dénition de f , cela correspond exactement au mgu du
système des équations.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Rappel : L'algorithme d'unication (1)
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
une signature Σ (un ensemble de symboles de fonction avec
leur arité). Dans le cas des équations de types on a
Rappel : L'algorithme d'unication (2)
I
Règles de transformation sur un système d'équations.
I
Decomposition :
g (s1 , . . . , sn ) = g (t1 , . . . , tn )
s1 = t1 , . . . , sn = tn
Σ = {p, f , int, bool, . . .}
I
où p , f ont arité 2, int et bool ont arité 0.
Un ensemble V de variables.
I
T (Σ, V ) : l'ensemble des termes construits sur Σ et V
I
free(t) : l'ensemble des variables qui paraissent dans le terme t
I
Clash :
g (s1 , . . . , sn ) = h(t1 , . . . , tm )
false
quand g diérent de h
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Le typage en OCaml
Rappel : L'algorithme d'unication (3)
I
Occur Check :
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
x =t
false
Rappel : L'algorithme d'unication (4)
I
Variable Orientation
I
Trivial Equation
quand x ∈ free(t), et t est diérent de x .
I
Variable Elimination :
x =t ∧φ
x = t ∧ φ[x/t]
t=x
x =t
quand t n'est pas une variable
x =x
true
quand x 6∈ free(t), x ∈ free(φ).
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Le typage en OCaml
Rappel : L'algorithme d'unication (5)
I
I
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Cet algorithme termine toujours : soit il donne false, soit un
système d'équations en forme normale (aucune transformation
ne s'applique).
Exemple : résolution d'équations (1)
I
Système de départ :
tf
= f (tx , f (ty , f (tz , t1 )))
tx
= f (t2 , f (t3 , t1 ))
Le résultat est équivalent au système d'origine.
ty
= f (tz , t2 )
Quand l'algorithme se termine avec un résultat diérent de
false : on a un système d'équations de la forme
t3 = p(ty , tz )
x1 = t1
..
.
xn = tn
où aucun des xi parait dans les termes tj .
I
Après élimination de t3 :
tf
= f (tx , f (ty , f (tz , t1 )))
tx
= f (t2 , f (p(ty , tz ), t1 ))
ty
= f (tz , t2 )
t3 = p(ty , tz )
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Le typage en OCaml
Exemple : résolution d'équations (2)
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Après élimination de t3 :
Exemple : résolution d'équations (3)
I
Après élimination de ty :
tf
= f (tx , f (ty , f (tz , t1 )))
tf
= f (tx , f (f (tz , t2 ), f (tz , t1 )))
tx
= f (t2 , f (p(ty , tz ), t1 ))
tx
= f (t2 , f (p(f (tz , t2 ), tz ), t1 ))
ty
= f (tz , t2 )
ty
= f (tz , t2 )
t3 = p(f (tz , t2 ), tz )
t3 = p(ty , tz )
I
Après élimination de ty :
I
tf
= f (tx , f (f (tz , t2 ), f (tz , t1 )))
tf
= f (f (t2 , f (p(f (tz , t2 ), tz ), t1 )), f (f (tz , t2 ), f (tz , t1 )))
tx
= f (t2 , f (p(f (tz , t2 ), tz ), t1 ))
tx
= f (t2 , f (p(f (tz , t2 ), tz ), t1 ))
ty
= f (tz , t2 )
ty
= f (tz , t2 )
t3 = p(f (tz , t2 ), tz )
t3 = p(f (tz , t2 ), tz )
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Exemple : résolution d'équations (4)
Exemple d'échec de l'inférence de type
I
I
Le mgu associe à tf le terme :
f (f (t2 , f (p(f (tz , t2 ), tz ), t1 )), f (f (tz , t2 ), f (tz , t1 )))
I
I
C-à-d, dans la notion habituelle de OCaml, le type de f est :
où, après renommage des variables (t2 7→ a, tz 7→ b , t1 7→ c ) :
(a → (b → a) × b) → c) → (b → a) → b → c
Regardons un deuxième exemple :
l e t f g = ( g 4 2 ) && ( g " c o o c o o " )
((t2 → (tz → t2 ) × tz ) → t1 ) → (tz → t2 ) → tz → t1
I
Après élimination de tx :
I
Système d'équations :
tf
= f (tg , bool)
tg
= f (int, bool)
tg
= f (string, bool)
On obtient avec les règles d'unication :
f (int, bool) = f (string, bool)
int = string
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Le typage en OCaml
Pourquoi cet echec ?
Quantication de variables de types
l e t f g = ( g 4 2 ) && ( g " c o o c o o " )
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
I
Pourquoi est-ce que g ne peut pas être du type :'a → bool ?
I
Pour voir la réponse il faut expliciter les quanticateur des
variables de types.
I
Toutes les variables dans un type sont quantiées
universellement au début du type :
I
Par exemple : Un type comme
a → b → a × (b → a)
est à lire comme
∀a, b :
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Quantication de variables de types
I
a → bool) → bool
Des types avec des ∀ sous une èche n'existent pas en OCaml,
l'inférence a raison de refuser cette dénition. Ce qui existe en
OCaml est le type
∀a :
((a → bool) → bool)
mais c'est un type diérent !
Normalement, dans tous les types les variables de types sont
(implicitement) quantiées ∀, avec un quanticateur devant le
type complèt.
Le type qu'on essaye de construire ici est :
(∀a :
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Reprenons l'exemple :
l e t f g = ( g 4 2 ) && ( g " c o o c o o " )
I
Les variables a, b peuvent être instanciées par des types
quelconques.
Le typage en OCaml
Quantication de variables de types
I
a → b → a × (b → a)
I
Normalement, les variables de types libres (non quantiées)
paraissent seulement pendant la résolution des équations, une
fois le type le plus général obtenu les variables sont quantiées.
I
I
Nous verrons une exception à cette règle un peu plus tard.
Dans un type dérivé pour f dans let f x1 ... xn =e, toutes
les nouvelles variables de type sont quantiées au début par ∀.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Example : Quantication de variables de type (1)
Example : Quantication de variables de type (2)
t2
I
z }| {
let f g h = fun x → h (g x)
| {z }
t
|
{z
}
I
La solution trouvée pour la variable tf est :
3
tf = (tx → t3 ) → (t3 → t2 ) → (tx → t2 )
t1
I
On obtient le système d'équations :
tf
= tg → (th → t1 )
t1 = tx → t2
I
Dans ce type, les variables tx , t2 , t3 sont libres, elles sont donc
implicitement quantiées avec un ∀
I
Le type obtenu pour f est donc à lire comme :
∀tx , t3 , t2 : (tx → t3 ) → (t3 → t2 ) → (tx → t2 )
th = t3 → t2
tg
= tx → t3
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Quelques résultats fondamentaux
I
I
Il existe un algorithme qui, étant donné une expression e,
trouve, si elle est typable, son type σ le plus général possible,
aussi appelé type principal
Le premier algorithme pour cela est le W de Damas et Milner,
qu'on trouve dans Principal type-schemes for functional
programs. 9th Symposium on Principles of programming
languages (POPL'82).
I
Cet algorithme utilise de façon essentielle l'algorithme
d'unication de Herbrand/Robinson.
I
Les algorithmes modernes utilisent plutôt directement la
résolution de contraintes.
I
À la surprise générale, en 1990 on a montré que l'inférence de
type pour le noyau de ML est DEXPTIME complète.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Exemples (inf4.ml)
l e t p x y = fun z −> z x y ; ;
let
let
let
let
let
let
let
let
x7 ; ;
x0
x1
x2
x3
x4
x5
x6
x7
=
=
=
=
=
=
=
=
fun x −> x i n
x0 x0 i n
x1 x1 i n
x2 x2 i n
x3 x3 i n
x4 x4 i n
x5 x5 i n
x6 x6 i n
p
p
p
p
p
p
p
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Le typage en OCaml
Typage et traits impératifs
Comment faire exploser le typeur d'OCaml...
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Dans l'exemple du transparent précédent : le type de xn a
taille 2n !
Les règles de typage pour OCaml : ...
I
Standard :
sommes
tuples
I enregistrements
Plus compliqué : value restriction
I typage des eets de bord (ce chapitre)
I
I
I
I
let
let
let
let
let
= fun
= fun
= fun
= fun
= fun
f 4 ( fun z −>
f0
f1
f2
f3
f4
x −>
y −>
y −>
y −>
y −>
z );;
(x , x)
f0 ( f0
f1 ( f1
f2 ( f2
f3 ( f3
in
y)
y)
y)
y)
I
in
in
in
in
Heureusement, en pratique, personne n'écrit de code comme
ça.
L'inférence de type à la ML reste un des système de type les
plus puissants.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
La value restriction
I
En OCaml, on dispose de structures mutables capables de
contenir des données de tout type.
I
Opérateurs pour les références :
ref ∀a : a → (a ref)
créer une référence vers une valeur
!
∀a : (a ref) → a
déréférencer
:= ∀a : (a ref) → a → unit changer la valeur d'une case
mémoire référencée
On peut imaginer que ref est un constructeur de type (au
même titre que →, ×, list, etc.)
I
I
Essayons d'appliquer notre algorithme d'inférence de types en
présence de références.
I
I
Avancé :
I
I
I
I
I
modules
récursion polymorphe
objets
variants polymorphes (voir la semaine prochaine)
GADT (voir dans deux semaines)
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Exemples (inf6.ml)
l e t c = r e f ( f u n c t i o n x −> x ) ; ;
c := ( f u n c t i o n x −> x + 1 ) ; ;
! c true ; ;
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Typage et traits impératifs
Inférence de types en présence de références
I
Selon notre algorithme, ref (function x -> x) a le type
(a → a) ref
I
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Donc, on obtient pour c le type :
∀a : (a → a) ref
I
Le quanticateur universel pour a va permettre d'instancier a
une fois par int, et puis par bool.
I
Évidement OCaml a raison de refuser ce code, il y a donc un
problème avec notre inférence de types.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Inférence de type en présence de référence
I
Quelles sont les conditions qui permettent de quantier les
variables de type ?
I
Une première idée est : l'expression ne contient pas du tout de
références.
I
Ca marche, mais a une conséquence assez grave : le
polymorphisme est eectivement desactivé dès qu'on utilise
des références dans une fonction, même si l'utilisation est
parfaitement sûre.
I
Regardons l'exemple suivant :
Restriction de quantication
I
Le malheur vient du fait que la variable 'a est quantiée avec
un ∀.
I
En vérité, une fois la variable 'a instanciée, on ne devrait plus
avoir le droit de changer cette instanciation.
I
En présence de traits impératifs, on ne peut donc pas
quantier les variables dans les types comme avant.
I
Idée : les variables de types sont quantiées seulement quand
l'expression à la droite du let satisfait certaines conditions,
sinon la variable reste libre et peu donc être instanciée une
seule fois.
I
Ces conditions restent à déterminer !
I
OCaml ache une variable de type libre comme '_a.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Exemples (value-restriction3.ml)
l e t f a s t r e v = f u n c t i o n l i s t −>
let l e f t = ref l i s t
and r i g h t = r e f [ ]
i n begin
w h i l e ! l e f t <> [ ] do
r i g h t := ( L i s t . hd ( ! l e f t ) ) : : ! r i g h t ;
l e f t := L i s t . t l ( ! l e f t )
done ;
! right
end ; ;
( ∗ OK ! ∗ )
fastrev [1;2;3;4];;
f a s t r e v [ true ; true ; f a l s e ; f a l s e ] ; ;
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Inférence de type en présence de référence
I
La question est alors : trouver la bonne condition sous laquelle
les variables de type peuvent être quantiées, tel que :
I
I
I
I
les erreurs de type pendant l'excution du programme sont
excluses ;
les fonctions polymorphes utilisant les références de façon sûre
restent autorisées.
Cette question a donné lieu à plusieurs propositions, toutes
assez complexes.
Une solution simple a été trouvée par Andrew K. Wright en
1995.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Exemples (value-restriction1.ml)
l e t c = r e f ( f u n c t i o n x −> x ) ; ;
( ∗ t y p e o f c : ( ' _a −> '_a) r e f , '_a i s a f r e e v a r i a b l e ! ∗ )
c := ( f u n c t i o n x −> x + 1 ) ; ;
c ;;
( ∗ t y p e o f c : ( i n t −> i n t ) r e f , '_a has been i n s t a n c i a t e d ∗ )
! c true ; ;
( ∗ t y p e e r r o r : c l a s h between i n t and b o o l ∗ )
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
La
Value Restriction
Solution simple introduite par SML : permettre la généralisation
seulement pour les valeurs (d'où le nom). Les valeurs sont :
I
les constantes (13, "foo", 13.0, ...)
I
les variables (x, y, ...)
I
les fonctions (fun x -> e)
I
les constructeurs appliqués à des valeurs (Foo v), excepté ref
I
une valeur avec une contrainte de type (v : t)
I
un n-uplet de valeurs (v1, v2, ...)
I
un enregistrement contenant seulement des valeurs {l1 = v1,
l2 = v2, ...}
I
une liste de valeurs [v1, v2, ...]
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Conséquences de la Value Restriction
I
Dans l'exemple précédent, c a le type (' _a −> '_a) ref
I
Explication : l'expression n'est pas une valeur, donc il n'y a pas
de généralisation (∀) lors du let.
I
Inconvénient : il y a parfois des programmes correctes qui sont
refusés, comme sur l'exemple suivant.
I
On peut normalement contourner le problème facilement.
I
OCaml utilise une solution légèrement plus générale, due à
Jacques Garrigue.
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Exemples (value-restriction2.ml)
l e t i d = f u n c t i o n x −> x ; ;
(∗ type e r r o r ∗)
let f = id id ; ;
f 42;;
f " coocoo " ; ;
( ∗ s o l u t i o n : eta − e x p a n s i o n ∗ )
l e t g = f u n c t i o n x −> ( i d i d ) x ; ;
g 42;;
g " coocoo " ; ;
Programmation Fonctionnelle Avancée 7 : Inférence de types, polymorphie et traits impératifs
Typage et traits impératifs
Pour en savoir plus
Harry G. Mairson.
Deciding ML typability is complete for deterministic
exponential time.
In Proceedings of the 17th ACM SIGPLAN-SIGACT
symposium on Principles of programming languages, POPL
'90, pages 382401, New York, NY, USA, 1990. ACM.
Andrew K. Wright.
Simple imperative polymorphism.
Lisp Symb. Comput., 8(4) :343355, December 1995.
Jacques Garrigue.
Relaxing the value restriction.
In FLOPS 2004, pages 196213.
Téléchargement