Plan des 4 prochains cours
-
Principes du typage statique
-
Types de données
Partie: I
Les principes du typage
Plan
-
Introduction au typage
-
Quelques exemples
-
Notations
-
Le typage de CAML
-
Modularité
-
traits impératifs
-
L'unification
Introduction I
Le typage
Diviser l'espace des valeurs en collections: un type est une
collection de valeurs partageant une même propriété
Les types sont toujours plus ou moins vérifiés:
-
soit à l'exécution (typage dynamique)
-
soit à la compilation (typage statique)
Intérêts du typage statique
-
détection précoce d'erreurs
-
documentation, structuration (une première information sur le
programme)
-
facilite les optimisations (enregistrements, accès,
représentations,...)
-
élimination de certaines erreurs d'exécution
-
sécurité du code
Introduction II
Remarque
-
le typage dynamique est plus précis
(
if B then 1 else 2+"a"
, avec B
= vrai)
-
le typage statique doit être conservatif
-
trouver des systèmes de type les plus riches possible
On étudie ici les principes du typage statique
Qu'est qu'un type?
-
Un type est une expression d'un langage de types
-
Déclarer un type = introduire un nom de type
-
Définir un type = associer un nom à une exp. de type
Typer une expression
-
Typer = associer un type à une expression
- Typage fort = toute expression a un type et un seul
Introduction III
Vérification des types
le programmeur associe un type à chaque déclaration d'identificateur
et le compilateur vérifie la correction (e.g, C, C++, Pascal, Ada,...)
int addition (int x1, int x2)
{ int temp;
temp = x1 + x2;
return(temp); }
Synthèse des types
les types sont devinés (on dira synthétisés ou inférés) par le compilateur par
une analyse des contraintes d'utilisation des variables (e.g, Caml, SML,...)
#let addition x1 x2 =
let temp = x1 + x2 in
temp;;
addition : int -> int -> int = <fun>
Introduction IV
``le programme P est de type ty''
P : ty
Vérification
-
vérifier que ty est un type valide pour P
Synthèse
Intérêts de la synthèse
-
concision, simplicité, facilité d'écriture
-
généralité plus grande des programmes
-
Un algorithme fondamental de l'informatique (IA)
Quelques exemples I
Typer une expression:
parcourir l'expression en associant
un type unique à chaque sous-expression
Exemple:
2 + (4 * 5)
Un axiome:
i : int
Une règle de déduction:
Quelques exemples II
-
des axiomes:
P:ty
-
des règles d'inférence
-
typer = construire un arbre où les feuilles sont des axiomes et les
noeuds, des règles d'inférence
Quelques exemples III
Un programme est bien typé lorsque l'on peut construire une telle déduction
Typer:
2 + (true * 5)
Question:
Que faire en présence d'identificateurs?
#let x = 1;;
x : int = 1
#let y = 2 + (4 * x);;
y : int = 6
On introduit l'environnement de typage.
-
contient des liaisons (idf,type)
-
ë (y:int)(x:int) û = Env
Notations, définitions I
``Dans l'environnement Env, le programme P a le type ty''
Env |- P:ty
L'environnement de typage
-
liste d'association ë (x1:ty1),...,(xn:tyn) û
-
une fonction d'accès acces tq:
acces (x,ë (x1:ty1),...,(xn:tyn) û) |
= |
ì í î |
ty1 si x = x1 |
acces (x,ë (x2:ty2),...,(xn:tyn) û)
sinon |
|
|
|
Notre langage des types
-
Types de base: unit, int, float, char,
string,...
-
Types construits
- types constants déclarés
- constructeur de type (opérateur sur les types). e.g, *
- types paramétrés, e.g, ('a,'b) arbre
-
Variables de type :a, b,... notés
'a,'b,...
Le typage de CAML I
Constantes
|
Env |- true : bool |
|
Env |- false : bool |
|
Env |- 42 : int |
|
Identificateurs
Conditionnelle
if
cond then
e1
else
e2
(Cond) |
Env |- cond : bool
Env |- e1 : ty
Env |- e2 : ty |
|
|
Env |- if cond
then e1
else e2 : ty |
|
|
Le typage de CAML II
Application de fonction
Soient e1 une expression (fonctionnelle) et e2 une expression.
L'application e1 e2 est typée avec la règle :
(App) |
Env |- e1 : t1 ® t2
Env |- e2 : t1 |
|
|
|
|
Exemples:
fact (fact 5)
2 + (4 * 5)
Remarque:
-
-> est associatif à droite
(t1 -> t2 -> t3 = t1 -> (t2 -> t3))
-
l'application est associative à gauche
e1 e2 e3 = (e1 e2) e3
Le typage de CAML III
Fonctions: function x -> corps
Typage dans Env
-
Choisir une nouvelle variable de type, t, pour désigner le
type de x. t est donc une inconnue.
- Typer corps dans
ë (x,t) <| Env û. Fait apparaître des
contraintes de typage (t = t1).
- Faire le bilan des contraintes :
-
contraintes contradictoires : erreur de typage.
- variables de type toutes déterminées:
si x : t1 et si corps: t2, alors
function x -> corps: t1 ® t2
- certaines variables de type restent indéterminées:
polymorphisme (plus tard)
(Fun) |
ë (x:t1) <| Env û |- corps : t2 |
|
|
Env |- function x -> corps : t1 ® t2 |
|
|
Le typage de CAML IV
Déclarations globales
let idf = exp;;
#let x = 1;;
x : int = 1
#let y = x+x;;
y : int = 2
#let y = y+2;;
y : int = 4
-
Typage dans l'env. global courant Env
-
typage de exp
Env |- exp : ty
-
le nouvel environnement global Env' = ë (idf:ty) <| Env û
Le typage de CAML V
Déclarations locales
let idf = exp1 in exp2;;
#let z = 1 in z+2;;
- : int = 3
#let y = z+1;;
Entrée interactive:
>let y = z+1;;
> ^
L'identificateur z n'est pas défini.
-
Typage de exp1 (Env |- exp1 : ty1)
-
Ajout du type obtenu à l'environnement Env_loc = ë (idf:ty1) <| Env û
-
Typage de exp2 dans Env_loc (Env' |- exp2 : ty2)
-
L'environnement global est inchangé
(Let) |
Env |- exp1 : t1
ë (idf:t1) <| Env û |- exp2 : t2 |
|
|
Env |- let idf = exp1 in exp2 : t2 |
|
|
Le typage de CAML VI
Fonctions récursives
une fonction récursive est une expression fonctionnelle déclarée par let rec.
let rec f = function x -> corps
#let rec fact1 = function n ->
if n <= 0 then 1
else n * fact1 (n-1);;
fact1 : int -> int = <fun>
Typage dans Env
-
Choisir deux nouvelles variables de type t1 et t2
pour désigner le type de f puis étendre Env
Env' = ë (f:t1 ® t2), (x:t1)<| Env û
-
typer corps (Env' |- corps : ty2)
-
Faire le bilan des contraintes (t1 = t1 et t2 = t2)
(Rec) |
ë (f:t1 ® t2), (x:t1)<| Env û |- corps : t2 |
|
|
Env |- rec f = function x -> corps : t1 ® t2 |
|
|
Le typage de CAML VII
Polymorphisme
Lors du typage, certaines variables de type restent
indéterminées. Le programme est très général !
#let app_tab f t =
for i = 0 to vect_length t - 1 do
t.(i) <- f (t.(i))
done;;
app_tab : ('a -> 'a) -> 'a vect -> unit =
<fun>
#let ajout n t =
app_tab (function x -> x+n) t;;
ajout : int -> int vect -> unit = <fun>
#let not_tab t =
app_tab (function x -> not x) t;;
not_tab : bool vect -> unit = <fun>
Intérêt du polymorphisme
-
généralité du code, concision
-
réutilisabilité (bibliothèques)
Le typage de CAML VIII
Les variables de type non déterminées sont généralisées.
Env |- function x -> x : a.a ® a
Schéma de type:
expression de type dont certaines variables, notées 'a, sont
quantifiées universellement en tête de l'expression:
a. a ® a. Les variables non
quantifiées, notées _'a, sont dites libres.
Type:
expression de type sans quantificateur.
Type monomorphe:
type ne contenant pas de variable de type
Type polymorphe:
schéma de type
Instancier un schéma de type:
remplacer les variables
universellement quantifiées par une expression de type.
Généraliser un type dans Env:
quantifier universellement
les variables du type qui ne sont pas libres dans Env.
Le typage de CAML IX
Typage d'une expression ne contenant que des identificateurs connus
Cas monomorphe: déjà vu.
Cas polymorphe:
a1 ... an.typ
instancier chaque ai par une nouvelle variable de type
ti.
On modifie la règle de typage des variables.
(Instance) |
instancier (acces (x,Env)) = ty |
|
|
|
|
avec:
instancier ( a1,...,an.t) =
t[t1/a1,...,tn/an]
Donc, maintenant, l'environnement est une liste d'association
(idf, schéma de type).
Le typage de CAML X
Exemple
#let id = function x -> x;;
id : 'a -> 'a = <fun>
#id true,id 1;;
- : bool * int = true, 1
#(function f -> f true,f 1) (function x -> x);;
Entrée interactive:
>(function f -> f true,f 1) (function x ->
x);;
> ^
Cette expression est de type int,
mais est utilisée avec le type bool.
Le typage de CAML XI
Quand et comment généraliser ?
au moment des déclarations
let idf = exp1 in exp2
On modifie donc un peu la règle de typage des déclarations.
Expression expansive:
effectuant des effets de bord pouvant
invalider le typage. En CAML, expressions de la forme e1 e2.
-
Env |- exp1 : t1
-
Si exp1 NON expansive, généraliser t1
en st1 = generaliserEnv(t1), sinon st1 = t1
-
Ajout du type obtenu à l'environnement
-
ë (x:st1) <| Env û |- exp2 : ty2
-
L'environnement global est inchangé
|
Env |- exp1 : t1
ë (idf:st1) <| Env û |- exp2 : t2 |
|
|
Env |- let idf = exp1 in exp2 : t2 |
|
|
Le typage de CAML XII
La généralisation
generaliser |
|
(typ) = t1,...,tn.typ
|
où les variables ti ne sont pas libres dans Env
Déclarations globales:
let id = exp;;
Même principe qu'auparavant mais la liaison (x:st1) est ajoutée à
l'environnement global.
Exemple
#let id = function x -> x;;
id : 'a -> 'a = <fun>
#let f = id id;;
f : '_a -> '_a = <fun>
#let g = function x -> (id id) x;;
g : 'a -> 'a = <fun>
Modularité I
Un exemple
#2 + 3;;
- : int = 5
#prefix +;;
- : int -> int -> int = <fun>
##open "float";;
#prefix +;;
- : float -> float -> float = <fun>
#1.2 + 1.3 ;;
- : float = 2.5
#2 + 3;;
Entrée interactive:
>2 + 3;;
>^
Cette expression est de type int,
mais est utilisée avec le type float.
Modularité II
#add_int 2 3;;
- : int = 5
##close "float";;
#2 + 3;;
- : int = 5
#1.2 + 1.3 ;;
Entrée interactive:
>1.2 + 1.3 ;;
>^^^
Cette expression est de type float,
mais est utilisée avec le type int.
Modularité III
Un module est défini par:
-
Un corps : définitions (types, fonctions, constantes, etc.)
regroupées dans un texte nommé mod.ml.
-
Une interface d'exportation nommée mod.mli qui définit les
déclarations visibles à l'extérieur: contient des déclarations de types
et de valeurs (nom, type).
-
Pour l'importateur: module = env. de typage (utilisé au cours du
typage)
-
si aucune interface mod.mli n'est définie, toutes les
déclarations sont exportées.
Modularité IV
Structure d'un environnement complet
ë Dû |´| ë
O û |´| ëC û
-
D : liaisons des déclarations du pgm. en cours
- O : liaisons des modules ouverts
- C : modules connus du système
Environnement initial EInit
-
D : vide
- O : modules de la librairie "core"
=>
man. de ref.
- C : modules connus du système
Modularité V
Construire un environnement par importation
-
C : les liaisons déclarées dans les interfaces mod.mli
connues du système sont accessibles avec la notation
mod__idf.
-
ouvrir mod: placer les liaisons de mod au début de O.
-
Si idf de mod déjà lié dans O
=>
masquage de la liaison antérieure, qui reste accessible avec la
notation complète.
-
Aucun masquage des liaisons de D.
Autre choix possible : pas de masquage dans O. Si conflit,
notation complète (choix de Ada).
A FAIRE
Vous connaissez un langage de programmation, qui n'est pas Caml-Light. Dans ce langage :
-
Quelle est la syntaxe des déclarations globales? Où doivent-elles être placées?
-
Même question avec les déclarations locales.
-
Si ce langage est typé, comment est faite la vérification/inférence
des types.
-
Si ce langage offre des bibliothèques, y a-t-il compilation séparée?
-
Comment sont gérés les noms des bibliothèques?
Traits impératifs I
#let counter = ref 0;;
counter : int ref = ref 0
#let add_counter n =
counter := !counter + n;;
add_counter : int -> unit = <fun>
#let afficher mess =
print_string mess;
print_newline ();;
afficher : string -> unit = <fun>
#exception Division_par_zero;;
L'exception Division_par_zero est
définie.
#let division x y =
if y = 0 then raise Division_par_zero
else x / y;;
division : int -> int -> int = <fun>
Traits impératifs II
Référence
(ref exp)
Affectation
(exp1 := exp2)
|
Env |- exp1 : t1 ref
Env |- exp2 : t1 |
|
|
Env |- exp1 := exp2 : unit |
|
|
Séquence
(exp1 ; exp2)
|
Env |- exp1 : t1 ;
Env |- exp2 : t2 |
|
|
|
|
Exceptions
(exception idf of typ;;)
Env |- idf : typ ® exn
Env |- raise : a.exn ® a
Un algorithme d'unification entre types
Un langage de types:
|
ty |
::= |
ty ® ty | ty * ty | int
| var |
|
var est un ensemble infini de variables a1,....
Substitution:
Une substitution est une fonction s: var ® ty étendue aux
types telle que:
-
s(t1 ® t2) = s(t1) ® s(t2)
-
s(t1 * t2) = s(t1) * s(t2)
-
s(int) = int
Unification:
Deux types t1 et t2 sont unifiables, ce que l'on notera
t1 º t2, s'il existe une substitution
s telle que:
s(t1) = s(t2)
Un algorithme d'unification entre types
Une substitution s'écrit [a1:t1,...,an:tn].
Connaissant la substitution S1, l'unification de t1 et t2
produit la nouvelle substitution S2. On le note:
S1 |- t1 º t2 : S2
Avec les règles:
|
S |- t º t : S |
|
S |- t1 º t'1 : S1
S1 |- t2 º t'2 : S2 |
|
|
S |- t1 ® t2 º t'1 ® t'2 : S2 |
|
|
|
|
S |- t1 º t'1 : S1
S1 |- t2 º t'2 : S2 |
|
|
S |- t1 * t2 º t'1 * t'2 : S2 |
|
|
|
|
S |- t1 º t2 : S2 S(a) = t1 |
|
|
|
|
|
|
|
Conclusion
-
Déclaration du type des identificateurs/synthèse du type
-
Vérification/inférence
-
Typage fort: une expression a un type unique
-
Garanties apportées par le typage fort
Extensions
-
surcharge
-
sous-typage, objets
-
modules
Partie: II
Les types de données
Plan
-
Introduction
-
types de base
-
types produit
-
types somme
-
types récursifs
Introduction
Des structures:
-
contruire des structures complexes à partir de structures de base
-
les types donnent une information sur ces structures
Des fonctions:
-
elle manipulent (construisent/ accèdent) ces structures de données.
-
le filtrage est un moyen de raisonner ``par cas'' sur la forme de ces
structures.
Types de base
Des collections finies d'éléments. Ils correspondent souvent à des valeurs
primitives de la machine.
Produit Cartésien I
Produit cartésien d'ensembles:
A × B = { (x,y) : x Î A, yÎ B }
Projections:
A × B ® A, A × B ® B
(A × B) × C ¹ A × (B × C) mais isomorphisme
Produit cartésien de deux types:
|
Env |- e1 : t1 Env |- e2 : t2 |
|
|
|
|
#fst;;
- : 'a * 'b -> 'a = <fun>
#snd ;;
- : 'a * 'b -> 'b = <fun>
Produit cartésien de n types:
t_1 * t_2 * ..* t_n
Attention:
t1 * t2 * t3 ¹ (t1 * t2) * t3
¹ t1 * (t2 * t3)
Projections à définir par l'utilisateur
Produit cartésien II
Paires
Un constructeur de type * d'arité 2, un constructeur
de valeur : la virgule ,
N-uplets
Un constructeur de type d'arité n formé de (n-1) *, un
constructeur de valeur formé de (n-1) virgules ,
Définitions par cas: filtrage
#let fst = function (x,y) -> x;;
fst : 'a * 'b -> 'a = <fun>
#let snd = function (x,y) -> y;;
snd : 'a * 'b -> 'b = <fun>
#let acceder3 = function (x,y,z,t) -> z;;
acceder3 : 'a * 'b * 'c * 'd -> 'c =
<fun>
#let double_zero = function
(0,0) -> true
| (x,y) -> false;;
double_zero : int * int -> bool = <fun>
Type Enregistrement I
Un type produit à composantes nommées.
Constructeur de type
donné par l'utilisateur
type ... = {lab_1 : t_1 ; .., lab_n : t_n}
Constructeur de valeur
syntaxe presque identique.
#type enfant = {nom : string ; age : int};;
Le type enfant est défini.
#let simon = {nom = "Simon" ; age = 9};;
simon : enfant = {nom = "Simon"; age = 9}
Notation pointée
#let nom {nom = n;age = a} = n;;
nom : enfant -> string = <fun>
#let nom {nom = n} = n;;
nom : enfant -> string = <fun>
#let nom x = x.nom;;
nom : enfant -> string = <fun>
Enregistrements III
#let anniv = function x ->
{nom = x.nom ; age = x.age + 1};;
anniv : enfant -> enfant = <fun>
#(anniv simon);;
- : enfant = {nom = "Simon"; age = 10}
#simon;;
- : enfant = {nom = "Simon"; age = 9}
Masquage
#type societe = {nom : string; numero : int};;
Le type societe est défini.
#let nom2 x = x.nom;;
nom2 : societe -> string = <fun>
Règle de typage
|
Env |- e : typ typ = {lbl1:t1;...;lbln:tn} |
|
|
|
|
Types somme I
Union disjointe d'ensembles:
t = A Å B = {x.g | x Î A} È {y.d | y Î B}
Injection canonique
-
gauche : A ® A Å B
-
droit : A ® A Å B
On a x:t ssi
$ y1:t1 tq x = g(y1) XOR
$ y2:t2 tq x = d(y2)
En Caml, ces injection sont données par l'utilisateur au moment de
la définition du type.
Types somme II
Syntaxe:
type .. = C1 of t1 | C2 of t2
Convention:
C of unit
Þ C
#type color = Coeur | Pique | Carreau | Trefle ;;
Le type color est défini.
#type carte = Dam of color
| Peti of int*color
| Jok ;;
Le type carte est défini.
Coeur, Dam, Peti constructeurs de valeur.
Prise en compte de la définition
On ajoute à l'env. global
Env les liaisons
ë (C1,t1 -> som), (C2,t2 -> som)<| Env û
Règle de typage
|
Env |- Ci : ti ® som Env |- e : ti |
|
|
|
|
Types somme III
Filtrage - Somme
#let valeurfoo = function
| Dam _ -> 20
| Peti (10, c) -> 10
| Peti (x, y) ->
if x = 9 & y = Coeur then 11 else 0
| Jok -> 1 ;;
valeurfoo : carte -> int = <fun>
#type unites = V of float | H of float;;
Le type unites est défini.
#let distance_parcourue =
function (V x, H y) -> x *. y ;;
Entrée interactive:
> function (V x, H y) -> x *. y ;;
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Attention: ce filtrage n'est pas
exhaustif.
distance_parcourue : unites * unites ->
float = <fun>
#distance_parcourue (V 25.0, H 4.0);;
- : float = 100.0
Types somme IV
Les motifs M sont définis ainsi:
|
M |
::= |
C | C M | (M,...,M) |
|
|
| true | false | i |
|
|
| x | _ |
|
où:
-
C est un constructeur de type
-
i désigne un entier
-
x, une variable
Types somme IV
Typage des fonction définies par filtrage
Un cas
|
ë (x1:t1),...,(xn:tn),Env û |- M : typ1 |
ë (x1:t1),...,(xn:tn),Env û |- exp : typ2 |
|
|
|
Env |- function M -> exp : typ1 ® typ2
|
|
où les xi sont les variables du motif M.
Plusieurs cas
|
Env |- : function M1 -> exp1 : typ1 ® typ2
|
... |
Env |- : function Mn -> expn : typ1 ® typ2 |
|
|
|
Env |- function |
| M1 -> exp1 |
... |
| Mn -> expn |
|
: typ1 ® typ2 |
|
|
Types récursifs II
#deriv "x" mon_exp;;
- : exp = P (C 1, P (C 0, C 0))
Attention : Syntaxe correcte ¬Þ sémantique définie
#type sans_val = C of sans_val * sans_val ;;
Le type sans_val est défini.
Conclusion
-
Un programme = des données + des fonctions
-
filtrage = raisonnement par cas sur la structure de ces données
Questions
-
représentation de ces données
-
qu'est ce qu'une valeur?
-
comment obtenir la valeur d'une expression?
Ce document a été traduit de LATEX par
HEVEA.