Typage et Sémantique - TD2
Paul Brauner
18 janvier 2010
Le langage que nous avons vu jusqu’à présent a permis d’illustrer les concepts de séman-
tique, typage et type safety. Cepedant ils ne permettent pas d’écrire de « vrais » programmes.
Nous verrons aujourd’hui comment en rajoutant des fonctions dans le langage on récupère
toute la puissance des langages de programmation usuels. Nous verrons également que, à
l’instar du langage étudié lors du TD1, ce langage doit être « dompté » par un système de
type si l’on veut que ses programmes se comportent correctement. Nous verrons enfin les
restrictions que cela entraîne sur les programmes si l’on se limite à un système naïf.
1 Une parenthèse : le λ-calcul
Certaines des notions abordées ci-dessous le sont de manière informelle. C’est le cas de l’α-équivalence
par exemple. Le TD ne portant pas sur la dymanique du langage mais plutôt sur les problème de typage,
cette approche est largement suffisante. Les plus intéressés peuvent se reporter à n’importe quel cours
introductif sur le λ-calcul, comme celui de Henk Barendregt.
1.1 Histoire
Au tableau. Voir le très bon Les métamorphoses du calcul de Gilles Dowek par exemple pour
de plus amples informations sur le sujet.
1.2 Syntaxe et sémantique
C’est un langage extrêmement simple dont les seules valeurs (ce qui est retourné à la fin)
sont des fonctions. Pour former ces fonctions on a le droit à une infinité de variables et à
une construction syntaxique qui signifie « la fonction qui à xassocie t». On peut aussi, pour
déclencher un calcul, appliquer une fonction à un argument. En voici la grammaire.
x,y,z,... variables
t::= xvariable
|λx.tabstraction
|t t application
On notera t,u,w,... les λ-termes. Dans λx.t, on dit de tqu’il constitue le corps de l’abstrac-
tion. De plus, on adopte les conventions suivantes.
La portée de l’abstraction s’étend aussi loin que possible. Concrètement, cela signifie
que λx.x y =λx.(x y)et non (λx.x)y.
L’application est associative à gauche. Concrètemenent, t u w =(t u)wet non t(u w).
1
Ces conventions sont celles adoptées par les langages et entre autres.
Avant de commencer, quelques exemples de λ-termes lus en langue naturelle :
λx.x: la fonction qui à xassocie x;
λx.x x : la fonction qui à xassocie xappliqué à x;
(λx.x x) (λy.y): la fonction qui à xassocie xappliqué à x, appliquée à la fonction qui à
yassocie y;
λf.λx.t: la fonction qui à fassocie la fonction qui à xassocie t;
. . .
Exercice 1. Représenter sous forme d’arbres les termes suivants. On représentera l’application par le
symbole @, au lieu d’un vide comme c’est le cas dans la grammaire.
fgh
(λx.fxx)u
(λf.λx.f x) (λy.y)
La fonction qui à xassocie xet la fonction qui à yassocie ysont intuitivement les mêmes.
On dit qu’elles sont α-équivalentes. On considère habituellement les λ-termes modulo cette
équivalence. Autrement dit, on confond ces deux fonctions, et on se donne le droit de renom-
mer les variables introduites par les abstractions quand cela nous arrange.
Exercice 2. Pour chacun des couples de λ-termes ci-dessous, dites s’ils sont α-équivalents ou non.
λx.xet λx.x x
λx.x x et λx.x x
λx.x x et λy.y y
λx.x y et λy.y y
λx.λy.x y et λy.λx.y x
En λ-calcul, il n’y a pas de fonctions à plusieurs arguments. À la place, on utilise une
astuce du mathématicien Haskell Curry, qui consiste à remplacer la fonction qui à (x,y)
associe tpar la fonction qui à xassocie la fonction qui à yassocie t.
(λ(x,y).t) (u,w)est simulé par (λx.λy.t)u w
Par exemple, en scheme :
Welcome to MzScheme v4.1.4 [3m], Copyright (c) 2004-2009 PLT Scheme Inc.
R5RS legacy support loaded
> ((lambda (x y) (+ x x y)) 2 3)
7
> (((lambda (x) (lambda (y) (+ x x y))) 2) 3)
7
Voyons à présent la sémantique opérationelle du langage. Plutôt que de s’encombrer de
définitions formelles comme la dernière fois, on se contente de dire qu’un pas d’exécution du
langage consiste en l’application de la règle suivante n’importe où dans le terme :
(λx.t)ut[x:= u]
où la notation t[x:= u]signifie « toù l’on a remplacé toutes les occurences de xpar u». Par
exemple (x+x+y)[x:= 42] = 42 +42 +y. Et c’est tout ! Il n’y a qu’une seule règle.
2
Attention . Il faut bien prendre garde à ne pas changer le sens d’une fonction lors de la substitution.
Par exemple (λy.x y)[x:= z] = λy.z y, mais si l’on applique naïvement la même substitution à
(λz.x z), on obtient λz.z z ce qui n’est plus du tout la même chose ! Pour éviter ce genre de problème,
on renomme λz.x z en λz0.x z0(par exemple) avant d’aller substituer xpar z.
On remarque que le langage est fortement indéterministe. En effet, à chaque pas, on peut
choisir d’appliquer la règle n’importe-où dans le terme.
Exercice 3. Exécutez les programmes suivant pas à pas selon la stratégie de votre choix.
λx.x
(λx.x) (λy.y)
(λf.λx.ffx) (λy.y)
(λx.λy.x) (λz.z) (λz.z z)
Ces termes ne sont pas très excitants. Church a montré comment on pouvait à proprement
parler programmer avec ce langage, en encodant la plupart des constructions de base des
langages actuels. Voyons deux exemples : (if ... then ... else) et les entiers.
Exercice 4. On décide d’appeller true le terme λx.λy.xet false le terme λx.λy.y. Écrivez une
fonction qui prend trois arguments : c,aet b, et qui se comporte comme if cthen aelse b.
Voyons à présent comment encoder les entiers.
Exercice 5 (Entiers de Church).On encode l’entier npar le terme λo.λs.s(s···(s o)···)s
apparaît nfois. Écrivez les encodages des entiers 0,1et 2. Écrivez les fonctions successeur, addition et
multiplication pour cet encodage des entiers.
Jusqu’à présent, tout se passe bien, ce qui est surprenant pour un langage aussi puissant.
Voyons un cas problématique.
Exercice 6.
1. Exécutez le terme (λx.x x) (λx.x x)que nous appellerons . Qu’observez-vous ?
2. Exécutez le terme (λx.λy.y)(λz.z)selon la stratégie de votre choix. Que se passe-t-il ? Quelle
est la stratégie adoptée par la plupart des langages de programmation ?
Pour illustrer ce dernier point, voici deux sessions interactives. Une dans l’interpréteur du
langage , l’autre dans celui du langage .
Welcome to MzScheme v4.1.4 [3m], Copyright (c) 2004-2009 PLT Scheme Inc.
R5RS legacy support loaded
> (define (omega) (omega))
> ((lambda (x y) y) (omega) 42)
L’interpréteur boucle sans fin.
GHCi, version 6.10.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> let omega () = omega ()
Prelude> (\x y -> y) (omega ()) 42
42
Prelude>
3
Ces deux comportements correspondent à deux stratégies d’évaluations différentes. Celle
de , la plus courament rencontrée dans les langages de programmation, est appelée
call by value1, tandis que celle de haskell, plus rare, est appelée call by name2. L’appel par valeur
consiste à évaluer les arguments d’une fonction avant d’appeler la fonction. L’appel par nom
passe les arguments tel quels et ne les évalue que s’ils sont utilisés. Voir discussion au tableau
concernant l’appel par nom.
2 Un langage de programmation avec des fonctions
Le λ-calcul seul étant un langage un peu austère, on lui ajoute les constructions vues dans
le TD1. On commence à se rapprocher d’un langage de programmation réaliste.
2.1 Langage non typé
En s’inspirant du λ-calcul, on ajoute des fonctions dans le langage du TD1.
t::= ··· |x|λx.t|t t
On étend la notion de valeur aux fonctions.
v::= ··· |λx.t
Enfin, pour que notre sémantique opérationnelle soit déterministe, on fixe une stratégie d’éva-
luation : l’appel par valeur, ce que traduisent les règles d’évaluation.
(λx.t)vt[x:= v] (E-AppAbs)
tt0
t u t0u(E-App1)tt0
v t v t0(E-App2)
Remarquez les valeurs, indiquées par un v, dans les règles de réduction. Celle de la règle
E-AppAbs signifie qu’une fonction n’est appelée que sur un argument évalué. Celle de la règle
E-App2signifie qu’on évalue l’argument de fseulement après que fa été évalué vers une
valeur, et on espère implicitement qu’il s’agit d’une fonction.
Exercice 7. Vers quoi le programme (λn.succ n) ((λx.x)0)s’évalue-t-il en un pas ? A-t-on le choix ?
Quelle est sa forme finale (après évaluation tant que possible) ?
Comme pour le langage du TD1, certains programmes ne terminent pas sur des valeurs.
Il y a bien sûr les programmes problématiques du TD1, qui sont inclus dans les programmes
que l’on peut former dans ce nouveau langage, mais il y en a de nouveaux.
Exercice 8. Donnez au moins trois programmes qui ne terminent pas sur une valeur et dont la forme
« bloquée » n’est pas exprimable dans le langage du TD1.
1appel par valeur
2appel par nom
4
2.2 Langage typé
Pour éviter ce genre de problèmes, on étend le système de types du TD1 aux nouvelles
construction du langage. L’idée est de donner le type ABà la fonction qui prend un
argument de type Aet renvoie une valeur de type B. Il y a donc maintenant une infinité de
types, dont voici la grammaire.
T::= Nat |Bool |TT
Un programme est alors bien typé si, chaque fois qu’on applique une fonction à un argument,
il est bien du type qu’elle attend. Pour qu’on puisse vérifier qu’un programme est bien typé,
il faut un peu d’aide de la part du programmeur : il doit préciser le type de l’argument d’une
fonction. On change donc légèrement la grammaire des programmes.
t::= ··· |λx :T.t|···
La construction λx :T.tse lit : « la fonction qui à xde type Tassocie t». Avant d’énoncer
formellement ce système de types, forgeons-nous une intuition de son fonctionnement.
Exercice 9. Les programmes suivant sont-ils bien typés ? Si oui donnez leur type.
λx :Bool.x
λx :Nat.succ x
λx :Bool.x x
λf :Nat (Nat Nat).λx :Nat.f x
λf :Nat Bool.if fthen (λx :Nat.true)else f
λf :Nat Bool.if (f0)then (λx :Nat.true)else f
On définit maintenant formellement la relation de typage. Cette fois, elle ne peut simple-
ment concerner un programme et un type. En effet, pour vérifier que λx :T.test bien de type
TU, on voudrait vérifier que ta le type Usachant que xa le type T. Ce « sachant que » se
traduit par l’introduction de la notion de contexte. Un contexte est un ensemble de couples
de la forme x:T, où xest une variable et Tun type. Par exemple {x:Bool,y:Nat Bool}
est un contexte. La relation de typage est alors une relation ternaire entre un contexte, un
programme et un type, qui s’écrit Γ`t:T, et se lit « ta le type Tdans le contexte Γ».
On adapte les règles du TD1 en leur rajoutant toutes un contexte. Il n’y a rien de nouveau.
Γ`true :Bool (T-True)
Γ`false :Bool (T-False)
Γ`t:Bool Γ`a:T Γ `b:T
Γ`if tthen aelse b:T(T-If)
Γ`t:Nat
Γ`iszero t:Bool (T-IsZero)
Γ`0:Nat (T-Zero)
Γ`t:Nat
Γ`succ t:Nat (T-Succ)
Γ`t:Nat
Γ`pred t:Nat (T-Pred)
5
1 / 6 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !