Le langage PCF
-
A
- Quelques particularités
-
Les fonctions.
- La récursion.
- B
- Un peu de rigueur
-
Syntaxe.
- Sémantique à petits pas.
- Sémantique à grands pas.
Un langage fonctionnel
-
PCF est un modèle de langage de programmation.
-
Dans un modèle, on simplifie, au profit d’un traitement rigoureux
et complet de la sémantique.
- PCF dit ce que les langages fonctionnels (ML, Lisp, Haskell)
ont en commun (leur noyau).
- PCF est d’abord un calcul des fonctions.
- Plus quelques constructions « pragmatiques »
(genre: l’arithmétique et ses quatre opérations).
Les constructions pragmatiques
L’arithmétique: syntaxe
-
Une infinité de constantes: 0, 1 etc.
- Quatre symboles binaires pour quatre opérations
+, -, *, /.
- Une conditionnelle, symbole ternaire Ifz t1 Then t2 Else t3.
Nous avons défini un certain ensemble de termes.
L’arithmétique: sémantique
On définit une infinité d’axiomes qui donnent le sens des
quatre opérations:
Résumé en:
Au passage: la soustraction est limitée aux entiers naturels.
x−y = 0 quand x ≤ y.
Cela revient à supposer que les entiers existent, ce qui est raisonable
Pour la conditionnelle, on précise:
Ifz 0 Then t1 Else t2—→t1
|
| Ifz n Then t1 Else t2—→t2 (n constante
entière différente de 0)
|
|
Et si les entiers n’existent pas ?
Les définir selon l’arithmétique de Peano:
-
Une constante 0 et un symbole unaire S (successeur)
Deux se note alors S (S 0).
- Des axiomes en nombre fini du genre:
| | …
Ifz 0 Then t1 Else t2—→t1
|
| Ifz S(t) Then t1 Else t2—→t2
|
|
Mais bon, on va supposer que les entiers existent.
Une fonction ?
En maths : soit f définie de ℕ dans ℕ, qui à n associe n × n.
Parfois écrit f: ℕ → ℕ, n ↦ n × n.
En PCF:
La différence: la liaison de la variable n est explicite.
Ces variables sont muettes, on aurait pu écrire:
La variable « muette » est déjà connue:
Que faire avec une fonction ?
L’appliquer (à un argument) notation t1 t2.
(Fun x -> x*x) 2
L’exécution du terme/programme ci dessus, doit bien renvoyer 4.
Note 1 La notation t1 t2 cache l’existence
d’un symbole binaire
“@” parfois explicité: @(t1,t2).
Note 2 L’application « associe à gauche »,
c’est à dire que t1 t2 t3 est à comprendre
comme (t1 t2) t3. Soit:
Sémantique de l’application
Ou plutôt du passage d’argument ou β réduction
(Fun x -> t1) t2—→[t2\x]t1
|
|
Où la notation [t2\x]t1 veut dire:
« t1 où x est remplacé par t2 » (on y reviendra).
Par exemple:
(Fun x -> x*x) 2
Devient
2*2
Qui, comme chacun sait, vaut 4.
Fonctions de première classe
-
L’argument d’une fonction peut être une fonction.
Fun f -> f 0
Appliquée à Fun x -> 1+x
la fonction ci-dessus renvoie: 1. - Le résultat d’une fonction peut être une fonction.
Fun n -> Fun m -> n + m
Appliquée à 1, la fonction ci-dessus renvoie:
Fun m -> 1+m
,
une fonction qui ajoute 1 à son argument.
Remarque : Avec les fonctions qui renvoient des fonctions, on
a aussi les fonctions à plusieurs arguments (curryfication).
Exemple classique de fonctions de première classe
La composition.
Fun f -> Fun g -> Fun x -> f (g x)
Ainsi:
(Fun f -> Fun g -> Fun x -> f (g x))
(Fun n -> n+1)
(Fun m -> 2*m)
Doit valoir (deux β-réductions, sur f puis g)
Fun x -> (Fun n -> n+1) ((Fun m -> 2*m) x)
Et donc (deux autres β-réductions, sur m puis n)
Fun x -> 2*x+1
Ce qui correspond bien à la composition.
La définition locale
Il est plus clair d’écrire:
Let comp = Fun f -> Fun g -> Fun x -> f (g x)) In
Let inc = Fun n -> n+1 In
Let twice = Fun m -> 2*m In
comp inc twice
En gros, l’idée c’est:
Let x = t1 In t2
∼
(Fun x -> t2) t1
|
|
Soit comme règle sémantique :
Let x = t1 In t2—→[t1\x]t2
|
|
La récursion
Comment définir la fonction puissance (ℕ2 → ℕ),
qui à x et n entiers associe:
-
En mathématiques (si on évite le ⋯ vite ambigu): récurrence.
- Dans les langages de programmation:
Définition « par point fixe »
En PCF on définit la fonction pow
comme égale à
Fun x -> Fun n -> Ifz n Then 1 Else x * pow x (n-1)
Mais pow
ne peut pas être utilisée dans sa propre définition.
On écrit plutôt p = Φ p
avec:
Let Φ =
Fun pow ->
Fun x -> Fun n -> Ifz n Then 1 Else x * pow x (n-1)
À comprendre comme: la fonction p
est point fixe de Φ.
On laisse de côté pour le moment la question de l’existence/unicité
de ce point fixe et on note :
Fix pow ->
Fun x -> Fun n -> Ifz n Then 1 Else x * pow x (n-1)
Réduction du Fix
Un moyen de contourner l’existence/unicité :
définir l’effet du Fix.
Fix x -> t—→[Fix x -> t\x]t
|
|
L’idée est en fait très simple:
(Fix fact -> Fun n -> Ifz n Then 1 Else n*fact (n-1)) 3
Règle du Fix, remplacer fact par Fix fact -> ….
(Fun n -> Ifz n Then 1 Else n * (Fix fact -> …) (n-1)) 3
β-règle (remplacer n par 3).
Ifz 3 Then 1 Else 3 * (Fix fact -> …) (3-1)
Comme 3 n’est pas nul, et que 3-1 vaut 2:
3 * (Fix fact -> …) 2
Soit en gros 3 * fact 2 c’est gagné. Au final 3 * 2 * 1 * 1.
Un peu de rigueur: syntaxe
Les termes de PCF sont définis, par
-
Une infinité de constantes 0, 1, etc.
- Quatre symboles d’opération +, -, *, / (binaires,
notés de façon infixe dans les termes t1 + t2).
- Un symbole ternaire (Ifz, notation des termes
Ifz t1 Then t2 Else t3).
- Un ensemble de variables.
- Le symbole binaire de fonction, termes notés Fun x -> t
(le premier argument est une variable, liée dans t).
- Le symbole binaire du point fixe, termes notés Fix x -> t.
- La définition, ternaire, termes notés Let x = t1 In t2.
Syntaxe abstraite de PCF
type var = string
type op = Add | Sub | Mul | Div
type t =
| Num of int
| Var of var
| Op of op * t * t
| Ifz of t * t * t
| Let of var * t * t
| App of t * t
| Fun of var * t
| Fix of var * t
Un peu de rigueur: sémantique
Le but est de définir une relation
t↪v entre les programmes (termes de PCF) et leur
valeurs.
Base de départ, six axiomes de réduction élémentaires:
(Fun x -> t1) t2—→[t2\x]t1 |
|
| Let x = t1 In t2—→[t1\x]t2 |
|
|
Fix x -> t—→[(Fix x -> t)\x]t |
|
|
Ifz 0 Then t1 Else t2—→t1 |
|
| Ifz n Then t1 Else t2—→t2 (n ≠ 0) |
|
| |
Sémantique « à petits pas »
Sémantique à petits pas
-
Rappel ? La fermeture réflexive-transitive R* d’une relation R.
| | t1 R* t2 t2 R* t3 |
|
t1 R* t3 |
|
|
- Les valeurs v sont définies comme les termes irréductibles
(ou formes normales)
- Définition de la sémantique t↪v ssi:
Autrement dit: réduire les radicaux de t, jusqu’à plus soif.
Exemples
Valeur de :
-
Fix x -> x
? Pas de valeur car t—→t et pas
le choix.
(Fun y -> 3) 2
? 3
en une β-réduction.
(Fun y -> 3) (Fix x -> x)
? 3
en
une β-réduction, si on est malin,
Fix n -> 1+n
? Pas de valeur,
réduction de t sur 1+t
, 1+(1+t)
, 1+(1+(1+t))
, etc.
Exemple plus sérieux
Let pow2 = Fix p -> Fun n -> Ifz n Then 1 Else 2 * p (n-1) In
pow2 2
Réduire liaison:
(Fix p -> Fun n -> Ifz n Then 1 Else 2 * p (n-1)) 2
Notons P = Fix p -> ⋯.
Réduire point fixe:
(Fun n -> Ifz n Then 1 Else 2 * P (n-1)) 2
β-réduction.
Ifz 2 Then 1 Else 2 * P (2-1)
2 * P (2-1)
2 * (Fun n -> Ifz n Then 1 Else 2 * P (n-1)) (2-1)
2 * (Ifz 2-1 Then 1 Else 2 * P ((2-1)-1))
2 * (Ifz 1 Then 1 Else 2 * P ((2-1)-1))
2 * (2 * P ((2-1)-1))
2 * (2 * ((Fun n -> Ifz n Then 1 Else 2 * P (n-1)) ((2-1)-1)))
2 * (2 * (Ifz ((2-1)-1) Then 1 Else 2 * P (((2-1)-1)-1))
2 * (2 * (Ifz (1-1) Then 1 Else 2 * P (((2-1)-1)-1))
2 * (2 * (Ifz 0 Then 1 Else 2 * P (((2-1)-1)-1))
2 * (2 * 1)
Deux multiplications, ouf 4.
Le déterminisme
Dans un terme, il peut y avoir plusieurs radicaux.
La réduction opère donc un choix.
Ce choix a-t-il un impact ? Les valeurs (formes normales) sont elles
uniques ?)
Confluence
L’unicité des formes normales découle du résultat plus fort de
confluence (Théorème, admis).
Qui dit en gros,
-
Deux calculs divergents (t2 ou t3).
- Ne le sont jamais définitivement (il existe t4).
Retour sur la substitution
On a dit : [t1\x]t2 = remplacer x par t1 dans t2.
La substitution donne le sens des variables.
Elle permet aussi de donner un sens aux expressions
qui possèdent des variables libres (ou non-closes).
Par ex. x+1.
possède un sens clair dès que l’on donne une valeur à x
.
Rappel ? définition des variables libres
| | F(t1 op t2) = F(t1) ⋃ F(t2)
|
| |
F(Fun x -> t) = F(t) ∖ { x }
|
| F(Fix x -> t) = F(t) ∖ { x }
|
| F(Let x = t1 In t2) =
F(t1) ⋃ (F(t2) ∖ { x })
|
|
Est-ce si simple ? En l’absence de lieurs, oui.
| | [t\x](t1 op t2) =
([t\x]t1) op ([t\x]t2)
|
|
[t\x](t1 t2) =
([t\x]t1) ([t\x]t2)
|
| [t\x](Ifz t1 Then t2 Else t3) =
Ifz [t\x]t1 Then [t\x]t2 Else [t\x]t3
|
|
Soit par exemple,
En présence de lieurs
La substitution donne la valeur des variables libres.
On propose donc:
[t\x](Fun x -> t1) = Fun x -> t1
|
| [t\x](Fun y -> t1) = Fun y -> [t\x]t1
|
| |
Let y = t In Fun x -> x + y
En substituant…
Alpha-équivalence
Dans l’exemple [x\y](Fun x -> x + y
),
nous sommes victimes de la capture de la variable libre x,
qui devient liée en traversant la liaison Fun x ->
…
Or
L’alpha-équivalence est l’égalité modulo le changement des variables muettes.
On considère les termes « modulo alpha-équivalence ».
Une définition de la substitution
Choisir un bon représentant des classes d’alpha-équivalence.
[t\x](Fun y -> t2) = Fun z -> [t\x]([z\y]t2)
|
|
[t\x](Fix y -> t2) = Fix z -> [t\x]([z\y]t2)
|
|
[t\x](Let y = t1 In t2) =
Let z = [t\x]t1 In [t\x]([z\y]t2)
|
|
Où z est tel que
z ∉F(t) (éviter capture vue)
et z ∉F(t2)
(éviter autre capture).
Plus les définitions pour les symboles sans lieurs.
Sémantique et exécution de programme
Dans un terme, il peut y avoir un choix de radicaux.
Programmes et réduction faible
-
Dans un programme, il n’y a pas d’inconnu :
on ne réduit que des termes clos.
- Les fonctions sont des fonction et point.
On réduit des termes clos et on utilise jamais la règle :
t—→t′ |
|
Fun x -> t—→Fun x -> t′ |
|
|
Tout cela revient à définir les valeurs v ainsi :
-
Une constante entière n.
- Ou bien une fonction Fun x -> t (t terme quelconque, avec
F(t) ⊆ { x }).
Une stratégie
C’est une sous-relation (⇒, ⊆) déterministe de la
relation de réduction (→).
Définissons « l’appel par nom », dont la principale caractéristique
est de ne pas réduire les arguments des fonctions.
Les axiomes ne changent pas (voir slide ).
t1—→nt′1 |
|
t1 op t2—→nt′1 op t2 |
|
| t2—→nt′2 |
|
v1 op t2—→nv1 op t′2 |
|
| t1—→nt′1 |
|
Ifz t1 Then t2 Else t3—→nIfz t′1 Then t2 Else t3 |
|
|
Autre présentation
Le radical réduit est le plus extérieur (et le plus à gauche).
Rn(Ifz t1 Then t2 Else t3) = Rn(t1)
|
|
-
Une étape: contracter Rn(t).
- Itérer (—→n*) jusqu’à trouver une valeur.
Présentation directe de l’appel par nom
En sémantique dite à grands pas, ou naturelle.
t1↪nFun x -> t3 [t2\x]t3↪nv |
|
t1 t2↪nv |
|
| |
| [t1\x]t2↪nv |
|
Let x = t1 In t2↪nv |
|
| [(Fix x -> t)\x]t↪nv |
|
Fix x -> t↪nv |
|
|
t1↪n0 t2↪nv |
|
Ifz t1 Then t2 Else t3↪nv |
|
| t1↪nn (n ≠ 0) t3↪nv |
|
Ifz t1 Then t2 Else t3↪nv |
|
| t1↪nn1 t2↪nn2 |
|
t1 op t2↪nn1 op n2 |
|
| |
Quelle présentation choisir ?
On préfère souvent la sémantique naturelle plus concise,
en particulier pour programmer un évaluateur (TP).
Mais, ne dit rien des termes qui ne terminent pas,
ne dit rien des termes « bloqués » (ex. 1
appliqué à 2
).
Les résultats théoriques sont donc plus précis en sémantique par
petit pas.
Équivalences (admises)
-
Si t↪nv, alors t—→n*v.
- Si t—→n*v (v valeur), alors t↪nv.
Une autre stratégie : l’appel par valeur
Par rapport à l’appel par nom, deux règles changent :
t1↪nFun x -> t3 [t2\x]t3↪nv |
|
t1 t2↪nv |
|
| [t1\x]t2↪nv |
|
Let x = t1 In t2↪nv |
|
|
Deviennent
t1↪vFun x -> t3
t2↪vv2
[v2\x]t3↪vv |
|
t1 t2↪vv |
|
| t1↪vv1 [v1\x]t2↪vv |
|
Let x = t1 In t2↪vv |
|
|
Note : Les arguments sont réduits avant la substitution.
Appel par valeur
Parfois caractérisé comme « contracter le radical le plus profond »
(et le plus à gauche).
Ce n’est pas tout à fait exact.
-
Le Ifz ne change pas.
Autrement dit on ne remplace pas
t1↪n0 t2↪nv2 |
|
Ifz t1 Then t2 Else t3↪nv2 |
|
|
par
t1↪v0 t2↪vv2 t3↪vv3 |
|
Ifz t1 Then t2 Else t3↪vv2 |
|
|
- Autrement dit, il reste un peu d’appel par nom.
Idem pour Fix x -> t.
Le termes t n’est pas réduit,
même si il contient un radical « plus profond ».
Manque d’expressivité de l’appel par valeur
On définit (Let) la function fi.
Fun x -> Fun y -> Fun z -> Ifz x Then z Else y
|
Comparaison des stratégies
Appel par nom : Algol 60, Haskell.
Appel par valeur : tout les autres langages (ML).
On démontre :
On constate que l’appel par valeur est plus facile à comprendre.
-
TP, divers évaluateurs.
- La prochaine fois, interprétation.
Ce document a été traduit de LATEX par HEVEA