Programmation Fonctionnelle Avancée 9 : Types phantomes et
Transcription
Programmation Fonctionnelle Avancée 9 : Types phantomes et
Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Les types fantômes (phantom types) Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT I Un type fantôme est un type polymorphe dont au moins un paramètre de type n'est pas utilisé dans sa dénition. En voici Ralf Treinen Université Paris Diderot UFR Informatique Institut de Recherche en Informatique Fondamentale [email protected] un : type 'a t = float ;; I Tant que les dénitions restent visibles, toutes les instances des types fantômes sont équivalentes pour le compilateur : I Les égalités char t = oat = string t sont connues du typeur, qui ne regarde que le corps de la dénition : oat . 28 novembre 2016 c Roberto Di Cosmo et Ralf Treinen Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Exemples (phantom1.ml) Les types fantômes (phantom types) type let let 'a t = float ;; (x : char t) = (y : string x +. y ; ; I Les choses deviennent bien plus intéressantes si ces égalités se 3.0;; t) = 5.0;; retrouvent cachées par la dénition d'un type abstrait dans l'interface d'un module. I Cela permet par exemple d'avoir deux versions diérentes du type float pour des unités de mesure diérentes. Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Exemples (phantom2.ml) Exemples (phantom3.ml) module type LENGTH = s i g type ' a t v a l m e t e r s : f l o a t −> [ ` M e t e r s ] v a l f e e t : f l o a t −> [ ` F e e t ] t v a l ( + . ) : ' a t −> ' a t −> ' a t v a l t o _ f l o a t : ' a t −> f l o a t end ; ; module L e n g t h : LENGTH = s t r u c t type ' a t = f l o a t l e t meters f = f let feet f = f l e t (+.) = P e r v a s i v e s . ( + . ) let to_float f = f end ; ; t open let let Length m1 = m e t e r s f1 = feet 10.;; 40.;; m1 +. m1 ; ; f1 +. f1 ; ; to_float m1 +. ( f1 +. f1 ) ; ; f1 ; ; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Même les meilleurs peuvent en avoir besoin Utiliser les types fantômes I On peut essayer de pousser cet exemple plus loin, et coder des informations plus nes dans le paramètre fantôme. I Dans l'exemple suivant, nous essayons d'appliquer ce principe pour raner un type de listes en listes vides et listes non vides. I Nous dénissons deux types abstraites vide et nonvide nous servent seulement pour faire cette distinction. qui Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes module type LISTE = s i g type v i d e type n o n v i d e type ( ' a , ' b ) t val l i s t e v i d e : ( vide , 'b) t v a l c o n s : ' b −> ( ' a , ' b ) t −> ( n o n v i d e , ' b ) t v a l head : ( n o n v i d e , ' b ) t −> ' b end ; ; module L i s t e : LISTE = s t r u c t type v i d e type n o n v i d e type ( ' a , ' b ) t = ' b l i s t let l i s t e v i d e = [ ] l e t cons v l = v : : l l e t head = f u n c t i o n [ ] −> a s s e r t f a l s e | a : : _ −> a end ; ; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Listes vides et non vides I L'exemple précédent permet de distinguer les listes vides et non vides aux niveau de typage. I Problème : quel est le type de la fonction tail ? I Son argument doit être du type (nonvide ,' b) t. Mais le résultat peut être vide ou non vide. I Si on utilise des variants polymorphes comme paramètres, le sous-typage des variants polymorphes donne des types plus précis pour les constructeurs. Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Exemples (phantom5.ml) open Liste ;; listevide ;; cons 3 head ( cons listevide ;; 3 listevide );; ( ∗ e r r e u r de t y p a g e ! ∗ ) head listevide ;; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes module type LISTEVP = s i g type ( ' a , ' b ) t val l i s t e v i d e : ( [ ` Vide ] , ' b ) t v a l c o n s : ' b −> ([ < ` V i d e | ` N o n v i d e ] , ' b ) t −> ( [ ` N o n v i d e ] , ' b ) t v a l head : ( [ ` N o n v i d e ] , ' b ) t −> ' b v a l t a i l : ( [ ` N o n v i d e ] , ' b ) t −> ([ < ` V i d e | ` N o n v i d e ] end ; ; module L i s t e V P : LISTEVP = s t r u c t type ( ' a , ' b ) t = ' b l i s t let l i s t e v i d e = [ ] l e t cons v l = v : : l l e t head = f u n c t i o n [ ] −> a s s e r t f a l s e | a : : _ −> a l e t t a i l = f u n c t i o n [ ] −> a s s e r t f a l s e | _ : : t −> t end ; ; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Exemples (phantom7.ml) Pourquoi cet erreur ? open I Notre système de type est maintenant trop faible pour éviter ListeVP ; ; des erreurs d'applications de fonctions à des listes vides. I La distinction en listevide ;; let x = cons 3 listevide ;; head x ;; head listevide ;; tail ( cons 1 tail ( tail ( cons non vide est trop grossière. I Pour faire cela nous devrons représenter chaque nombre naturel par un type diérent : nous utilisons un type zero, et un constructeur de type ' a succ. listevide );; listevide ));; et I Idée : coder la longueur de la liste dans le type ! (∗ type e r r o r ∗) 1 vide I Ainsi, le nombre naturel 2 est représenté par le type zero succ succ (notation postx pour les applications de (∗ a s s e r t f a i l u r e ∗) Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes module t y p e LISTECOUNT = s i g type ( ' a , ' b ) t type zero type ' a succ v a l l i s t e v i d e : ( zero , ' b ) t v a l e s t v i d e : ( ' a , ' b ) t −> b o o l v a l c o n s : ' b −> ( ' a , ' b ) t −> ( ' a s u c c , ' b ) t v a l head : ( ' a s u c c , ' b ) t −> ' b v a l t a i l : ( ' a s u c c , ' b ) t −> ( ' a , ' b ) t end ; ; module L i s t e c o u n t : LISTECOUNT = s t r u c t type ( ' a , ' b ) t = ' b l i s t type zero type ' a succ let listevide = [] l e t e s t v i d e = f u n c t i o n [ ] −> t r u e | _ −> f a l s e l e t cons v l = v : : l l e t head = f u n c t i o n [ ] −> a s s e r t f a l s e | a : : _ −> a l e t t a i l = f u n c t i o n [ ] −> a s s e r t f a l s e | _ : : l −> l end ; ; constructeurs de type). Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Exemples (phantom9.ml) open Listecount ; ; let l 4 = cons 1 let l1 = ( tail tail head l1 ; ; tail ( tail l1 ) ; ; ( cons 2 ( cons ( tail 3 l4 ) ) ; ; ( cons 4 listevide )));; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Listes avec codage de longueur Cas d'utilisation : Lablgtk2 On utilise les types fantômes pour typer les widgets (avec des variants polymorphes) : I Les deux derniers transparents poussent l'exercice encore plus loin : on encode la longueur de la liste dans un type fantôme (en codage unaire). I L'utilité est limitée : on peut encore écrire map avec ces types de données, mais pas la fonction append (pourquoi ?). type type type type (−'a ) gtkobj widget = [ ` widget ] container box = = [ ` widget | ` container ] [ ` w i d g e t | ` c o n t a i n e r | ` box ] Cela autorise la coercion sûre entre les classes : (mybox : box gtkobj :> container gtkobj ) Voir http://lablgtk.forge.ocamlcore.org/ et le message de Jacques Garrigue de septembre 2001 http://caml.inria.fr/pub/ml-archives/caml-list/2001/ 09/2915ad47e671450ac5acefe4d8bd76fb.en.html Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Cas d'utilisation : contrôle d'accès dans Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes libvirt I Libvirt is a C toolkit to interact with the virtualization capabilities of recent versions of Linux (and other OSes). The library aims at providing a long term stable C API for dierent virtualization mechanisms. It currently supports QEMU, KVM, XEN, OpenVZ, LXC, and VirtualBox. I Libvirt utilise des types fantômes pour le contrôle d'accès aux ressources virtualisées. http://camltastic.blogspot.fr/2008/05/ phantom-types.html I Voir : Exemples (libvirt1.ml) module type CONNECTION = s i g type ' a t v a l c o n n e c t _ r e a d o n l y : u n i t −> [ ` R e a d o n l y ] t v a l c o n n e c t : u n i t −> [ ` R e a d o n l y | ` R e a d w r i t e ] v a l s t a t u s : [ > ` R e a d o n l y ] t −> i n t v a l d e s t r o y : [ > ` R e a d w r i t e ] t −> u n i t end ; ; module C o n n e c t i o n : CONNECTION = s t r u c t type ' a t = i n t l e t count = r e f 0 l e t connect_readonly () = i n c r count ; ! count l e t connect () = i n c r count ; ! count let status c = c let destroy c = () end ; ; t Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes module t y p e HTML = s i g t y p e +'a e l t v a l p c d a t a : s t r i n g −> [> ` Pcdata ] e l t v a l s p a n : [< ` Span | ` Pcdata ] e l t l i s t −> [> ` Span ] e l t v a l d i v : [< ` Div | ` Span | ` Pcdata ] e l t −> [> ` Div ] e l t end ;; Exemples (libvirt2.ml) open C o n n e c t i o n ; ; l e t conn_ro = c o n n e c t _ r e a d o n l y status destroy let conn_ro ; ; (∗ e r r o r ∗) conn_rw = c o n n e c t status destroy ();; conn_ro ; ; module Html : HTML = struct t y p e raw = | Node o f s t r i n g ∗ raw l i s t | Pcdata o f s t r i n g t y p e ' a e l t = raw l e t p c d a t a s = Pcdata s l e t d i v l = Node ( " d i v " , l ) l e t s p a n l = Node ( " s p a n " , l ) end ; ; ();; conn_rw ; ; conn_rw ; ; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Exemples (html2.ml) Bilan des types fantômes open Html ; ; let x = div [ pcdata "" list ];; avantages : ( ∗ e r r e u r , pas de d i v dans l e s span ∗ ) l e t x ' = span [ d i v [ ] ] ; ; ( ∗ OK ∗ ) l e t x ' ' = span [ span [ ] l e t f s = div [ s ; pcdata l e t f ' s = d i v [ s ; span l e t f ' ' s = d i v [ s ; span I étiquettes sur les types qui permettent I un contrôle statique du bon usage des données I sans aucune pénalité à l'exécution (les types sont eacés à la compilation) ];; limitations : expressivité limitée "" ] ; ; []];; []; pcdata "" ];; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types fantômes Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Pour en savoir plus Limitation des types algébriques I Il y a des situations dans lesquelles Daan Leijen and Erik Meijer. on sait qu'un ltrage est complet, mais le compilateur OCaml ne le voit pas. Domain specic embedded compilers. SIGPLAN Not., 35(1) :109122, December 1999. I C'est le cas dans le code (simplié) suivant : dans la deuxième branche, nous savons que x = a:: r, donc le deuxième match est exhaustive. Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples (gadt1.ml) Vers des GADT I De l'anglais : let | | silly x = match −> 1 a : : r −> match x I Il s'agit d'une extension du langage (contrairement aux types with phantômes). [] x with (a ' : : r ') Generalised Algebraic Data Types −> ( ∗ p a t t e r n matching non e x h a u s t i v e ∗ ) 2;; I Première étape de l'extension : on sépare constructeurs de la le type envoyé par les dénition du type : cela permet de préciser, et distinguer, le résultat de chaque constructeur. I Syntaxe alternative (et plus générale) pour déclarer les constructeurs d'un type somme I Attention : à partir d'ici nous avons besoin de OCaml > 4.00. Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples (gadt2.ml) Exemples (gadt3.ml) type type type empty nonempty 'a | Cons | Nil il : : = int ∗ empty 'a il −> nonempty il il ( ∗ j u s q u ' i c i , t o u t va b i e n ∗ ) l e t h e a d = f u n c t i o n C o n s ( x , r ) −> x ;; ( ∗ mais l e t y p e u r e x i g e que t o u s l e s m o t i f s ont l e meme t y p e ! ∗ ) l e t tail_or_empty = function −> | Nil | ( C o n s _) ( ∗ On p e u t a i d e r l e t y p e u r , e t l u i d i r e que t a i l _ o r _ e m p t y e s t une f o n c t i o n a y a n t un t y p e p o l y m o r p h e . Chaque c a s p e u t donc a v o i r une i n s t a n c e d i f f e r e n t e de ce t y p e . ∗) let tail_or_empty −> | Nil | ( C o n s _) : type a . a as −> l hd = function 1 as l −> hd l ;; Exemples (gadt4.ml) Exemples (gadt5.ml) ( ∗ V e r s i o n de s i l l y q u i ne donne p l u s de w a r n i n g ∗ ) −> int l ;; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés silly −> 1 Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés let il : | Nil | ( C o n s _) type a . a il −> int = function 1 as l −> ( ∗ h e r e we know t h a t ' a = nonempty ! ∗ ) match l with C o n s ( x , _) −> 2 ; ; ( ∗ Exemple : l e s l i s t e s v i d e s / n o n v i d e s , e n c o r e ∗ ) type type type empty nonempty ( 'a , 'b) : t = | Nil | Cons : ( empty , let hd = function 'b ∗ 'b) ( 'a , t 'b) Cons t −> (x , r ) ( nonempty , −> x ;; 'b) t ;; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples (gadt7.ml) Exemples réalistes : interpréteur (sans GADT) l e t re c l e n g t h = function −> | Nil | ( Cons l e t re c : type b. fun f −> t int I Interpréteur pour un petit langage d'expressions : type −> a 1 + ( length b c. (a , b) = (a , b) 0 (_, r ) ) map a (b t −> −> r );; I Types de base int, bool, plus un (a , c) P(int,P(bool,bool)), t ( Cons (x , r )) −> Cons ( f . . .) I On veut avoir un seul type d'expressions −> f u n c t i o n | N i l −> N i l | x , map f r );; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples (gadt8.ml) Exemples (gadt9.ml) l e t re c | ( ∗ e x p r e s s i o n s s a n s GADT, a l a L3 ∗ ) type | | | | expr = of i n t B o o l of b o o l P r o d of e x p r ∗ e x p r I f T h e n E l s e of e x p r ∗ (∗ v a l e u r s ∗) type r e s = I of eval Int x Bool | Prod | IfThenElse match ∗ = function −> I x b −> B b ( e1 , e 2 ) −> | Int expr P. I Nombre inni de types d'expressions (int, bool, P(int,bool), −> c) constructeur de type | B b | _ ( eval ( e1 , e2 , e 3 ) ( eval −> i f −> P e1 , e v a l e2 ) −> with then e v a l e1 ) b failwith else e2 " Nonboolean eval condition e3 in an ITE ! " ; ; expr ; ; ( ∗ e x c e p t i o n p e n da n t l ' e x e c u t i o n ∗ ) int | B of bool | P of eval res ∗ ( IfThenElse (( Int 0) ,( Int 1) ,( Int 2)));; res ; ; ( ∗ OK ∗ ) eval ( IfThenElse ( ( Bool eval ( IfThenElse ( ( Bool true ) , ( I n t 3) ,( I n t 4 ) ) ) ; ; true ) , ( Bool f a l s e ) , ( Bool true ))) Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Limitations de l'interpréteur sans GADT Exemples (gadt10.ml) ( ∗ e x p r e s s i o n a v e c GADT ∗ ) Comme on ne sait pas classier les expressions par leur type, on est type obligé de : I aplatir les expressions dans un type expr | I aplatir les résultats dans un type res | I écrire la fonction eval en traitant les possibles cas d'erreur qui viennent du fait que toute expression peut apparaître partout | | _ expr = −> i n t e x p r B o o l : b o o l −> b o o l e x p r P r o d : ' a e x p r ∗ ' b e x p r −> IfThenElse : bool expr ∗ ' a −> ' a e x p r Int : int ( 'a expr ∗ 'b) expr 'a expr ∗ ;; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples (gadt11.ml) Exemples réalistes : fonctions composables l e t re c eval type : | Int | Bool −> x b −> b | Prod ( e1 , e 2 ) | IfThenElse if x ( eval a. a expr −> a = function fonctions qui se composent ? −> ( eval ( b , e1 , e 2 ) b) then e1 ) , ( e v a l e2 ) f ( eval e1 ) else ( IfThenElse (( Int 0) ,( Int f f 1 2 3 n A3 −→ . . . −→ An+1 A1 −→ A2 −→ −> ( eval e2 ) ; ; f I Cela semble impossible avec le langage vu en L3. I Avec GADT, on peut donner un type précis qui permet d'écrire ( ∗ e r r e u r de t y p a g e ! ∗ ) eval I Comment typer une liste [ f1 ; f2 ; f3 ;...; fn ] contenant des 1) ,( Int 2)));; du code bien typé. Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples (gadt12.ml) Variables de types et GADT type ( 'a , ' b) | Nilf : | Consf : cfl = ( 'a , ' a) cfl −> 'b) ( 'a ∗ ( 'b , ' c) cfl l e t re c c o m p u t e : type a b . a −> = fun x −> function | N i l f −> x ( ∗ h e r e ' a = ' b ∗ ) | Consf (f , rl ) −> let bad = C o n s f ( ( let good = C o n s f ( ( compute fun x −> (f −> (a , b) x) ( 'a , ' c) cfl −> cfl ;; b I Dans cet exemple, on voit qu'on peut utiliser des variables de type qui n'apparaissent pas dans le résultat du constructeur : c'est le cas de ' b. I Il s'agit de variables dites existentielles, parce que le type de Consf ∀acb.(a → b) ∗ (b, c) cfl → (a, c) cfl rl ;; truncate x) , peut se lire comme ∀ac(∃b.(a → b) ∗ (b, c) cfl) → (a, c) cfl Consf ( s t ri ng _o f _f lo at , N i l f ) ) ; ; fun x −> truncate x) , Consf ( string_of_int , N i l f ) ) ; ; compute 3.5 good ; ; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Exemples réalistes : fonctions d'impression (sans GADT) functional unparsing d'Olivier Danvy, le format est combinaison de fonctions d'achage élémentaires. I Avec le une I On peut donner un type précis au format et à la fonction d'impression : Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés type ' a t y = | Int : i n t ty | String : s t r i n g ty | P a i r : ( ' a t y ∗ ' b t y ) −> ( ' a ∗ ' b ) t y ; ; l e t r e c p r i n t f : type a . a t y −> a −> u n i t = fun f o r m a t v −> match f o r m a t with | I n t −> p r i n t _ i n t v ( ∗ i c i v e s t i n t ∗ ) | S t r i n g −> p r i n t _ s t r i n g v ( ∗ i c i v e s t s t r i n g ∗ ) | P a i r ( b , c ) −> p r i n t f b ( f s t v ) ; p r i n t f c ( snd v ) (∗ i c i v e s t une p a i r e ∗) ;; l e t ( ∗∗ ) = fun x y −> P a i r ( x , y ) ; ; p r i n t f ( ( I n t ∗∗ S t r i n g ) ∗∗ I n t ) ( ( 4 , " and t h e n " ) , 5 ) ; ; Programmation Fonctionnelle Avancée 9 : Types phantomes et GADT Types algébriques généralisés Pour en savoir plus Hongwei Xi, Chiyan Chen, and Gang Chen. Guarded recursive datatype constructors. In Proceedings of the 30th ACM SIGPLAN Symposium on Principles of Programming Languages, pages 224235, New Orleans, January 2003. François Pottier and Yann Régis-Gianas. Stratied type inference for generalized algebraic data types. In Conference record of the 33rd ACM SIGPLAN-SIGACT symposium on Principles of programming languages, POPL '06, pages 232244, New York, NY, USA, 2006. ACM. Dimitrios Vytiniotis, Simon L. Peyton Jones, Tom Schrijvers, and Martin Sulzmann. Outsidein(x) modular type inference with local assumptions. J. Funct. Program., 21(4-5) :333412, 2011.