Programmation fonctionnelle en langage Scheme - Support

publicité
Programmation fonctionnelle en langage Scheme
- Support PG104 -
M.Desainte-Catherine et David Renault
ENSEIRB-MATMECA, département d’informatique
January 25, 2016
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Objectifs pédagogiques
Objectif général
Découverte de la programmation fonctionnelle pure à travers :
I Les formes qu’elle prend syntaxiquement (expressions, fonctions et listes
récursives)
I Les méthodes qu’elle permet de déployer.
Compétences générales attendues
I Spécifier un calcul de façon fonctionnelle plutôt qu’impérative : programmer au
moyen d’expressions plutôt que d’instructions
I Spécifier des calculs récursivement plutôt qu’itérativement
I Spécifier un calcul générique : abstraire un calcul au moyen de paramètres
fonctionnels et de retours fonctionnels
I Comparer des solutions selon le style et la complexité
Ce support est accessible en version électronique mise à jour régulièrement à l’adresse :
http://www.labri.fr/perso/renault/working/teaching/schemeprog/schemeprog.php
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Concepts et terminologie
Concepts fonctionnels
I Écriture fonctionnelle : programmation par applications de fonctions plutôt que
par l’exécution de séquences d’instructions
I Transparence référentielle : chaque expression peut être remplacée par son
résultat sans changer le comportement du programme
I Programmation fonctionnelle pure : sans effets de bords, avec transparence
référentielle.
I Fonctions de première classe : type fonction, constantes fonction, opérateurs
sur les fonctions
Autres concepts nouveaux
I Typage dynamique : Les variables sont typées au moment de l’exécution et non
au moment de la compilation
I Références : ce sont des adresses sur des objets, elles sont utilisées chaque fois
que les contenus ne sont pas utiles (passages de paramètres, retours de
fonctions)
I Garbage collector ou ramasse-miettes : gestion dynamique et automatique de
la mémoire. L’utilisateur ne s’occupe pas de désallouer la mémoire.
Transparence référentielle en C
Ces fonctions de la bibliothèque C respectent-elles la transparence
référentielle?
I
int abs(int j); oui ou non
I
int rand(void); oui ou non
Transparence référentielle en C
int y = 0;
int f(x) {
return x + y++;
}
void g() {
int i= f(1);
int j= f(1);
}
int y = 0;
int f(x) {
return x + y;
}
void g() {
int i= f(1);
int j= f(1);
}
I Ce code respecte-il la
transparence référentielle?
oui ou non
I Ce code respecte-il la
transparence référentielle?
oui ou non
I Si non à cause de quelle
instruction? int y = 0;
I Si non à cause de quelle
instruction? int y = 0;
return x + y++;
return x + y;
int i= f(1);
int i= f(1);
int j= f(1);
int j= f(1);
Transparence référentielle en C
int y = 0;
int f (x) {
r e t u r n x + y++;
}
void g () {
i n t i= f ( 1 ) ; / ∗ i =1 ∗ /
i n t j= f ( 1 ) ; / ∗ j =2 ∗ /
}
——————————–
int y = 0;
int f (x) {
r e t u r n x + y++;
}
void g () {
i n t j= f ( 1 ) ; / ∗ j =1 ∗ /
i n t i= f ( 1 ) ; / ∗ i =2 ∗ /
}
int y = 0;
int f(x) {
return x + y;
}
void g() {
int i= f(1);
int j= f(1);
}
I Ce code respecte-il la
transparence référentielle?
oui ou non
I Si non à cause de quelle
instruction? int y = 0;
return x + y;
int i= f(1);
int j= f(1);
Historique
Les langages des années 1950
I
FORTRAN (1954) : calcul scientifique, données et calcul numérique.
I
Lisp (1958) : calcul symbolique, données et algorithmes complexes (IA), démonstrations automatiques,
jeux etc.
I
Algol (1958) : langage algorithmique et structuré, récursivité.
Les langages lisp
I
1958 : John Mac Carthy (MIT)
I
1986 : common lisp ANSI (portabilité, consistence, expressivité, efficacité, stabilité).
I
Les enfants de lisp :
I
I
I
I
I
I
Logo (1968), langage visuel pédagogique
Smalltalk (1969, Palo Alto Research Center de Xerox) premier langage orienté objets
ML (1973, R. Milner, University of Edinburgh), preuves formelles, typage statique, puis CaML
(1987 INRIA), projet coq, et Haskell purement fonctionnel, paresseux.
Scheme (1975, Steele et Sussman MIT) mieux défini sémantiquement, portée lexicale, fermeture,
et continuations de première classe.
emace-lisp (Stallman 1975, Gosling 1982), langage d’extension de GNU Emacs.
CLOS (1989, Common Lisp Object System), common lisp orienté objets.
Le λ-calcul
Théorie des fonctions d’Alonzo Church (1930), modèle universel de calcul, directeur
de thèse d’Alan Turing (machines de Turing, théorie de la calculabilité).
Syntaxe – λ-termes
I Variables : x, y , ...
I Applications : si u et v sont des λ-termes uv est aussi un λ-terme. On peut
alors voir u comme une fonction et v comme un argument, uv étant alors
l’image de v par la fonction u.
I Abstractions : si x est une variable et u un λ-terme alors λx.u est un λ-terme.
Intuitivement, λx.u est la fonction qui à x associe u.
Exemple
I Constante : λx.y
I Identité : λx.x
I Fonction renvoyant une fonction : λx.λy .a
I Application : xyz ou ((xy )z)
Remarques: les applications sont faites de gauche à droite en l’absence de
parenthèses, une occurrence de variable est dite muette ou liée si elle apparaı̂t dans le
corps d’un λ-terme dont elle est paramètre, sinon elle est dite libre.
Syntaxe du λ-calcul
Soient x et y des variables et u et v des λ-termes
I Les λ-termes suivants sont-ils bien formés : oui ou non
I
I
I
I
I
I
I
I
I
x
λx . u
λλx. u
λx . y
λx u
(λx . u) y
(λu . u) y
(λx . u) v
((x u) y)
I Dans les λ-termes suivants, les occurences de la variable x sont-elles
libres ou liées ?
I ((u x) y)
I λy . u x
I λx . u x
Le λ-calcul – la substitution
Cette opération permet de remplacer les occurrences d’une variable par un terme pour
réaliser le calcul des λ-termes. On note t[x := u] la substitution dans un lambda
terme t de toutes les occurrences d’une variable x par un terme u.
Exemple
Dans ces exemples, les symboles x, y , z, a sont des variables.
I Dans une application : xyz[y := a] = xaz
I Dans une abstraction (cas normal) : λx.xy [y := a] = λx.xa
I Capture de variable libre : λx.xy [y := ax] = λz.zax(et non λx.xax),
renommage de la variable liée
I Substitution inopérante (sur variable liée): λx.xy [x := z] = λz.zy = λx.xy
Définition
I Variable: si t est une variable alors t[x := u] = u si x = t et t sinon
I Application: si t = vw alors t[x := u] = v [x := u]w [x := u] si v et w sont des
termes.
I Abstraction: si t = λy .v alors t[x := u] = λy .(v [x := u]) si x =
6 y et y n’est
pas une variable libre de u. Si y est une variable libre de u, on renomme y avant
de substituer. Si x = y le résultat est t.
Substitution en λ-calcul
Soient x, y, z t et a des variables, votez pour une des réponses pour chaque résultat de
substitution.
I xzty [t := ax] = xzaxy ou xzty ou xzty
I xztyt[t := ax] = xzaxyt ou xzaxyax ou xztyt
I λz.xz[x := yz] = λz.yzz ou λz.xz ou λt.yzt
I λy .xyz[z := a] = λy .xya ou λz.xza ou λy .xyz
I λy .xyz[y := a] = λy .xaz ou λy .xyz ou λa.xaz
Le λ-calcul – la β-réduction
On appelle rédex un terme de la forme (λx.u)v . On définit alors la β-réduction
(λx.u)v −→ u[x := v ]
I La réduction du terme (λx.u)v est la valeur de la fonction λx.u appliquée à la
variable v .
I u est l’image de x par la fonction (λx.u),
I L’image de v est obtenue en substituant dans u, x par v .
Exemple
I (λx.xy )a donne xy [x := a] = ay
I (λx.y )a donne y [x := a] = y
Remarque Les termes sont des arbres avec des noeuds binaires (applications), des
noeuds unaires (les λ-abstractions) et des feuilles (les variables). Les réductions
permettent de modifier l’arbre, cependant l’arbre n’est pas forcément plus petit après
l’opération. Par exemple, si l’on réduit
(λx.xxx)(λx.xxx)
on obtient
(λx.xxx)(λx.xxx)(λx.xxx)
β-réductions en λ-calcul
Soient x, y, z t et a des variables, votez pour un résultat de β-réductions.
I (λx.x) a −→ λa.a ou a
I (λx.x) (λy.a) −→ λy.a ou λx.x
I (λx.x) ((λy.a) z) −→ (λy.a) z ou a ou λx.x
I (λx.x) ((λy.a) (λy.yt)) −→ (λx.x) a ou (λy.a) (λy.yt) ou a
I (λxy.xay) z t −→ zat ou λxy .xay) z t
I (λx.(λy.xay)) z t −→ (λy.zay) t ou zat ou (λx.xaz) t
Le λ-calcul – la normalisation
Un lambda-terme t est dit en forme normale si aucune β-réduction ne peut lui être
appliquée, c’est-à-dire si t ne contient aucun rédex. Ou encore, s’il n’existe aucun
lambda-terme u tel que t −→ u.
Remarques
I On peut simuler la normalisation des λ-termes à l’aide d’une machine de Turing,
et simuler une machine de Turing par des λ-termes.
I Différentes stratégies de réduction sont définies dans le λ-calcul : stratégie
applicative (par valeur, dans les langages lisp et scheme), stratégie paresseuse
(par nom, dans Haskell).
I La normalisation est un calcul confluent. Soient t, u1 et u2 des lambda-termes
tels que t −→ u1 et t −→ u2. Alors il existe un λ-terme v tel que u1 −→ v et
u2 −→ v . Conséquence : l’ordre d’évaluation des arguments d’une fonction n’a
pas d’influence sur le résultat.
Exemple
Les symboles x, y , z, a sont des variables. Soit le terme (λx.y )((λz.zz)a)
I Stratégie applicative : (λx.y )((λz.zz)a) −→ (λx.y )aa −→ y
I Stratégie paresseuse :(λx.y )((λz.zz)a) −→ y
Lien avec la syntaxe lisp
La syntaxe lisp est complètement basée sur le λ-calcul. Les parenthèses servent à
délimiter les termes et les applications.
I Variables : x, et constantes de types numériques, symbolique, fonctionnel etc.
I Abstractions fonctionnelles : λx.xy s’écrit (lambda(x) (x y))
I Application : uv s’écrit (u v)
I Cas d’une abstraction fonctionnelle : ((lambda(x) (x y)) a)
I Cas d’une fonction nommée f (variable) ; fx s’écrit ( f x)
Exemple
I Application d’une abstraction fonctionnelle
I ((lambda (x) (x y)) a) −→ (a y)
I ((lambda (z) (x z)) a) −→ (x a)
I ((lambda (x y) (x z y)) a b) −→ (a z b)
I Application d’une fonction nommée
I Soit f la fonction (lambda (x) (x y))
(f a) = ((lambda (x) (x y)) a) −→ (a y)
I Soit f la fonction (lambda (x) (+ x 1)), avec + correspondant à
l’opération d’addition :
(f 2) = ((lambda (x) (+ x 1)) 2) −→ (+ 2 1) −→ 3
Syntaxe et évaluation lisp
I ((lambda (z) (x z)) a) −→ (a z) ou (x a) ou incorrect
I ((lambda (z) (x z)) a x) −→ (a z x) ou (x a x) ou incorrect
I ((lambda (x) (+ 3 x)) 10) −→ (+ 3 10) −→ 13 ou incorrect ou
(+ 10 3) −→ 13
I ((lambda (z) (lambda (x) (x z))) a) −→ (lambda (x) (x a)) ou
(lambda (z) (a z)) ou incorrect ou (a z)
I ((lambda (x y) (z y)) a b) −→ (a b) ou (z b) ou incorrect
Environnement de développement
Boucle Read Eval Print : REPL
1. Read : Lecture d’une expression
2. Eval : calcul (réduction) de l’expression
3. Print : affichage du résultat (forme normale)
4. Affichage du prompt > et retour à 1
I Top-level : niveau de la REPL, l’imbrication des expressions induit plusieurs
niveaux. Par exemple pour l’expression (+ 3 4 (* 1 2) 3)
−→ évaluation de (* 1 2)
−→ résultat 3
−→ évaluation de (+ 3 4 3 3)
−→ résultat 13
I Notation
> (+ 3 4 ( ∗ 1 2 ) 3 )
3
Expressions
Expressions symboliques
On appelle expressions symboliques (sexpr) les formes syntaxiquement correctes :
I Objet (nb, chaı̂ne, symbole, etc.)
I Expression composée (sexpr sexpr ... sexpr) : liste de sexpr. Utilisé à la fois pour
le code et les données :
I Notation de l’application d’une fonction à ses arguments.
I Notation des listesa : ’(e1 e2 ... en)
a
Pour éviter l’application et fabriquer une liste, il faut la faire précéder d’une quote
Evaluation
I Objets auto-évaluants : objet lui-même (nombres, booléens, caractères, chaı̂nes
de caractères).
I Symboles : valeur associée (identificateurs)
I Expression symbolique composée : application – évaluation de l’objet en
position fonctionnelle (la première), évaluation les arguments, puis application
de la fonction aux arguments et renvoie du résultat.
Expressions
Stratégie d’évaluation
Cet algorithme d’évaluation correspond à la stratégie applicative : les
arguments sont évalués en premier et c’est leur valeur qui est utilisée
dans l’application.
Formes spéciales
Il existe des opérateurs spéciaux pour lesquels les arguments ne sont pas
évalués selon l’algorithme précédent. Chacun a sa propre règle
d’évaluation. Notamment les opérateurs booléens et conditionnels : or,
and, if, cond... Ces expressions sont appelées formes spéciales. On y
trouve aussi en particulier, les expressions avec effets de bord :
affectation, déclarations, affichage.
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Résumé des constructions syntaxiques du langage
Types
I
Simples : Boolean, Number,
Character, String, Symbol
I
Conteneurs : Pair, List , Vector
I
Fonctions : Procedure
Variables et liaisons
I
Locales : let, let∗, letrec, letrec∗,
let−values, let∗−values
I
Globales ou locales : define
Formes
Expression ou formes define, let, lambda,
if , cond, set!, etc.
Expressions
constante, (f a1 a2 ... an )
Procédures
lambda
Macros
define−syntax
Continuations
call-with-current-continuation
Bibliothèques
library, import, export
Les types
Les booléens
I
Constantes : #t et #f
I
Toute valeur différente de #f est vraie
I
L’objet #t est utilisé comme valeur vrai, quand aucune autre valeur
ne paraı̂t plus pertinente.
I
Prédicat boolean?
Opérations booléennes
I
and : stop au premier argument évalué à faux
I
or : stop au premier argument évalué à vrai
I
not
I
nand, nor, xor, implies
Les opérateurs and, or , nand, nor et xor
admettent n arguments, n ≥ 0.
Exemple
> ( and )
#t
> ( or )
#f
Les types
Tour des types numériques
I
Number
I
Complex
I
Real
I
Rational
I
Integer
Exactitude
I
Prédicat : exact?
Les nombres
Les entiers
I
Taille non limitée (seulement par la mémoire)
I
Prédicat : integer ?
Exemple
> ( i n t e g e r ? 1)
#t
> ( integer ? 2.3)
#f
> ( integer ? 4.0)
#t
> ( e x a c t ? 4)
#t
Les nombres
Les rationnels
I
523/123
I
Accesseurs : numerator, denominator
I
Prédicats : rational?
Les réels
I
23.2e10
I
Prédicat : real ?
Exemple
> ( r e a l ? 1)
#t
> ( exact ? 4.0)
#f
Les nombres
Les complexes
I
3+2i
I
Contructeurs : make−polar, make−rectangular
I
Accesseurs : real−part, imag−part, magnitude, angle
I
Prédicat : complex?
Exemple
> ( s q r t −1)
0+1 i
> ( complex ? −1)
#t
> ( r e a l ? 1+2 i )
#f
Prédicats numériques
I
Nombres : zero?, positive ?, negative?
I
Entiers : even?, odd?
I
Comparaisons : = <
I
Égalités et inégalités sur les complexes.
I
Nombre d’opérandes supérieur à 2.
Exemple
> (= 1 2 3 )
#f
> (= 1 1 1 )
#t
<= >
>=
sur les réels.
Opérations numériques
Arithmétiques
Unaires
Exponentiation
Modulaires
+, −, ∗, /
add1, sub1
max et min
sqr, sqrt, log
exp
expt
modulo
quotient, remainder
quotient/remainder
gcd, lcm, abs
Arrondis
floor, ceiling ,
truncate, round
Trigonométrie
sin, cos, tan,
asin, acos, atan
n arguments réels, n ≥ 0
incrémentation, décrémentation
n arguments réels, n > 0
exponentielle naturelle
base arbitraire, exposant
renvoie 2 valeurs
Les caractères et les chaı̂nes de caractères
Constantes
I
Caractère : #\a
I
Chaı̂ne : ”de caracteres ”
Prédicats
I
Type :char?, string?
I
Comparaisons : char=?, char<?, char>?,
string=?, string<?, string>?.
Fonctions
I
Constructeurs : make−string, string
I
Accesseurs : string−ref
I
Longueur : string−length
I
Conversion : number−>string, string−>number
Les symboles
Constantes
Suite de caractères alphabétiques et numériques plus les caractères
suivants :
! $%&*+-. /: <=>? ˜
Identificateurs de variables et de fonctions, données symboliques.
Prédicats
I
Type : symbol?
I
Égalité : eq?
Conversions
I
symbol−>string, string−>symbol
Expressions booléenes et symboles
Quels sont est les résultats des expressions suivantes?
I
(and 1 2) : #t ou #f ou 1 ou 2
I
(and 2 #f) : #t ou #f ou 2
I
(and 1 (/ 1 0)) : 1 ou Division by zero
I
(or 1 (/ 1 0)) : 1 ou Division by zero
I
(symbol? 1) : #t ou #f
Les expressions conditionnelles
if
La forme if
( i f hconditioni halorsi hsinoni )
I
hconditioni, halorsi et hsinoni sont des expressions
I
Si hconditioni vaut vrai, le résultat est la valeur de l’expression halorsi
I
Sinon, le résultat est la valeur de l’expression hsinoni
Exemple
>
2
>
4
>
;
( i f 1 2 3)
( i f (= 1 2 ) 3 4 )
( i f (= 1 2 ) 3 )
e r r o r −> i f : m i s s i n g an ” e l s e ” e x p r e s s i o n
Utilisation de l’expression if
Quels sont les résultats des expressions suivantes?
1. (if (> 1 2) 3 4) : 3 ou 4
2. (+ (if (> 1 2) 3 4) 5) : 8 ou 9 ou Error
Les expressions conditionnelles
when - unless
La forme when
( when hconditioni he1 i . . . hen i )
Cette forme évalue les expressions hei i et renvoie le résultat de la dernière
quand l’expression hconditioni vaut vrai.
La forme unless
( u n l e s s hconditioni he1 i . . . hen i )
Même chose mais quand l’expression hconditioni vaut faux.
Les expressions conditionnelles
cond
La forme cond
( cond hc1 i . . . hcn i )
I
Les hci i sont des clauses : [hconditioni he1 i ... hen i ]
I
hconditioni, he1 i, ... hen i sont des expressions
I
Évaluation des conditions des clauses dans l’ordre de hc1 i à hcn i
I
Soit ci =[c e1 ... en ] la première clause dont la condition c vaut vrai,
les ei sont évaluées dans l’ordre et le résultat est celui de en .
Exemple
( cond [ ( number ? x ) ”X e s t un nombre ” ]
[ ( s y m b o l ? x ) ”X e s t un s y m b o l e ” ]
[ else ( . . . ) ] )
Les crochets définissant les clauses peuvent être remplacés par des
parenthèses, conformément à la norme R6RS du langage Scheme
Utilisation de l’expression cond
Quels sont les résultats des expressions suivantes?
I
(cond [(= 1 2) 1] [(< 2 3) 2] [else 3]) : 1 ou 2 ou 3
I
(cond [(= 1 2) 1] [#f 2] [else 3]) : 1 ou 2 ou 3
I
(cond [(= 1 2) 1] [0 2] [else 3]) : 1 ou 2 ou 3
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Les définitions
L’environnement est formé de liaisons symbole −→ valeur . Les symboles ne sont pas
typés (non déclarés), mais leurs valeurs le sont. Il s’agit d’un typage dynamique.
La forme define
I Variables : (define hv i hei)
I Fonctions : (define (hf i hp1 i hp2 i ... hpn i) he1 i he2 i ... hen i)
I Résultat non spécifié par la norme
Une définition établit une liaison entre une variable et un objet résultant de
l’évaluation de l’expression, cet objet pouvant être une fonction.
Exemple
> ( d ef i n e a 0)
> ( define ( f x ) x )
> ( d e f i n e g ( lambda ( x ) ( ∗ 2 x ) ) )
> a
0
> ( f 1)
1
> ( g 1)
1
2
error
Définitions et évaluation
I Quelle est la forme correcte pour définir une fonction :
(define g(x) x) ou (define (f x) x)
I Quelle est la forme correcte pour appliquer une fonction : f(1) ou (f 1)
I Le symbole e dans cette définition est-il relié à une fonction ou bien à un
numérique ? (define e (* 2 4))
I Le symbole e dans cette définition est-il relié à une fonction ou bien à un
numérique ? (define (e) (* 2 4))
I Soient les définitions suivantes :
(define (f) 1)
(define (g x) (* 2 x))
Parmi les expressions suivantes lesquelles sont valides :
I
(f 1)
(f)
(f 1 2)
I
(g 1)
(g)
(g #t)
Définitions de variables locales
La forme let
( l e t ( hl1 i
hl2 i
...
hln i )
hei )
I
hli i est une liaison : (hsi i hoi i)
I
hsi i est un symbole (id. de variable)
I
hoi i une valeur d’initialisation
I
hei est une expression
I
Résultat de l’évaluation de l’expression hei
dans l’environnement créé
L’évaluation des valeurs d’initialisation est effectuée en premier
puis les variables locales sont créées. Ce qui implique que les
valeurs des variables locales définies dans un let ne sont pas
utilisées dans l’évaluation des expressions d’initialisation.
Définitions de variables locales
La forme let∗
( l e t ∗ ( hl1 i
hl2 i
...
hln i )
hei )
I
hli i est une liaison : (hsi i hoi i)
I
hsi i est un symbole (id. de variable)
I
hoi i une valeur d’initialisation
I
hei est une expression
I
Résultat de l’évaluation de l’expression hei
dans l’environnement créé
L’évaluation des expressions d’initialisation est effectuée après
la création des variables locales.
Définitions de variables locales
Équivalence des formes Let et Lambda
La forme let est une forme fonctionnelle car elle équivaut à l’application
d’une fonction construite avec la forme lambda.
(let ((j 0))
(* x j))
((lambda(j) (* x j)) 0)
Question
La forme let∗ est-elle fonctionnelle? oui ou non
Différencier les expressions let et let*
Quels sont les résultats des expressions suivantes?
( define b 0)
( l e t ( ( b 1)
( c 2))
(+ b c ) )
2 ou 3
( define b 0)
( l e t ( ( b 1)
(c b))
(+ c b ) )
1 ou 2
( define b 0)
( l e t ∗ ( ( b 1)
(c b))
(+ c b ) )
1 ou 2
Stratégies de recherche d’une liaison
Problème: L’occurrence d’un symbole peut correspondre à plusieurs liaisons
dans l’environnement. Laquelle faut-il choisir?
Exemples : Que vaut i lors du calcul de f(3)?
Variable libre d’une
fonction : non défini dans
l’environnement local.
Paramètre d’une fonction
int i=0;
int
f(int i)
{
return i; 3 0
}
Variable locale à une fonction
int i=0;
int f(int x)
{
int i=3;
return i*x; 3
}
int i=0;
int
int f(int x)
{
return i*x; 2
}
Appel à la fonction f
0
int g()
{
int i=2;
return f(3);
}
0
Stratégies de recherche d’une liaison
Pour chercher la liaison correspondant à l’occurrence d’un symbole dans une
expression, la recherche commence par l’environnement dans lequel apparaı̂t
l’expression. Si l’expression apparaı̂t dans le corps d’une fonction et qu’aucune liaison
ne correspond (cas d’une variable libre), deux stratégies existent.
Stratégie lexicale
La stratégie lexicale consiste à remonter les environnements locaux englobants du
plus proche jusqu’à l’environnement global.
La première liaison dont le nom de symbole correspond est retenue. Cette stratégie
s’applique aussi à l’évaluation du corps d’une fonction lors d’une application. En effet,
celui-ci est évalué dans l’environnement englobant de la fonction, dit environnement
lexical.
Cette stratégie correspond au langage C et aux langages impératifs en général et au
langage Scheme.
Stratégie dynamique
Pour chercher la liaison correspondant à l’occurrence d’un symbole dans une
expression située dans le corps d’une fonction, la stratégie dynamique consiste à
rechercher sa liaison dans l’environnement dynamique, c’est-à-dire l’environnement
d’application de la fonction.
Cette stratégie correspond par exemple à LaTeX, et beaucoup de lisp dont emacs-lisp.
Common-Lisp implémente les deux stratégies.
Portées lexicales et dynamiques
Racket
> (define i 0)
> (define (f x) (* x i))
> (f 3)
0
> (let ((i 2)) (f 3))
0
> (let ((j 0))
(let ((g (lambda(x)
(* x j))))
(let ((j 3))
(g 3))))
0
emacs-lisp
(let ((j 0))
(flet ((g (x)
(* x j)))
(let ((j 2))
(g 3)))) -> 6
Common Lisp
(defvar i 0)
(defun f(x) (* x i))
(let ((i 2)) (f 3)) -> 6
(f 3) -> 0
emacs-lisp
(defvar i 0)
(defun f(x) (* x i))
(let ((i 2)) (f 3)) -> 6
(f 3) -> 0
(let ((j 0))
(flet ((g (x)
(* x j)))
(let ((j 2))
(g 3)))) -> 0
Portée dynamique en LaTeX
I
Style book : saute 2 pages avant la table of contents
I
Style report : saute 1 page avant la table of contents
I
On souhaite ne sauter qu’une page dans le style book
I
Commande cleardoublepage saute 2 pages
I
Commande clearpage saute 1 page
I
Commande renewcommand redéfinit une commande
Exemple
%% creation d’un bloc avec environnement local
\begingroup
\renewcommand{\cleardoublepage}{\clearpage}
\tableofcontents
\endgroup
%% sortie du bloc,
\cleardoublepage
%% cleardoublepage restauree
Portée et durée de vie en Scheme
Portée lexicale
La portée d’une liaison est la partie du code source dans laquelle il est
possible de l’utiliser.
I
Les liaisons globales ont une portée égale à tout le programme.
I
Les liaisons locales ont une portée limitée à la forme de définition
let.
Durée de vie
La durée de vie d’un objet correspond à la période de l’exécution d’un
programme comprise entre la création de cet objet et sa destruction.
I
Les objets définis globalement ont une durée durée de vie égale à
celle du programme.
I
Les objets définis localement ont une durée de vie potentiellement
égale à celle du programme.
Les environnements
I
La stratégie lexicale implique que la portée d’une variable peut être
déterminée lors de : la lecture ou l’exécution du programme
I
Scheme est un langage lexical ou dynamique
I
Dans la définition de la fonction f, la variable a est dite :
libre ou liée
I
Le résultat de l’expression suivante est : 1 ou 0
( let (( a 1))
( f 0))
Exemple
( define a 0)
( define ( f n)
( i f ( zero ? n)
a
0))
Portée et paradigme fonctionnel
La portée lexicale correspond-elle au paradigme fonctionnel?
I
I
Quelles sont les caractéristiques de la programmation fonctionnelle?
I
Typage dynamique
I
Fondement provenant du λ-calcul
I
Programmation par applications de fonctions
I
Transparence référentielle
Reformulation de la question
Portée et transparence référentielle
Les deux applications ( f 3) sont effectuées dans deux environnements différents. Le
premier lie i avec 0 et le second lie i avec 2.
Portée lexicale : Scheme
Les résultats des deux applications sont-ils les mêmes? oui ou non
> (define i 0)
> (define (f x) (* x i))
> (f 3)
> (let ((i 2)) (f 3) )
Portée dynamique : emacs-lisp
Les résultats des deux applications sont-ils les mêmes? oui ou non
(defvar i 0)
(defun f(x) (* x i))
(let ((i 2)) (f 3) )
(f 3)
Portée : formulation fonctionnelle
Méthode
On utilise la formulation fonctionnelle du let pour étudier le phénomène
de portée en calculant le résultat sur les exemples précédents..
(let ((j 0))
(let ((g (lambda(x)
(* x j))))
(let ((j 3))
(g 3))))
((lambda(j)
((lambda(g)
((lambda(j)
(g 3))
3))
(lambda(x) (* x j))))
0)
Portée dynamique – RMS Stallman
Some language designers believe that dynamic binding should be avoided,
and explicit argument passing should be used instead. Imagine that
function A binds the variable FOO, and calls the function B, which calls
the function C, and C uses the value of FOO. Supposedly A should pass
the value as an argument to B, which should pass it as an argument to C.
This cannot be done in an extensible system, however, because the author
of the system cannot know what all the parameters will be. Imagine that
the functions A and C are part of a user extension, while B is part of the
standard system. The variable FOO does not exist in the standard system;
it is part of the extension. To use explicit argument passing would require
adding a new argument to B, which means rewriting B and everything
that calls B. In the most common case, B is the editor command
dispatcher loop, which is called from an awful number of places.
What’s worse, C must also be passed an additional argument. B doesn’t
refer to C by name (C did not exist when B was written). It probably
finds a pointer to C in the command dispatch table. This means that the
same call which sometimes calls C might equally well call any editor
command definition. So all the editing commands must be rewritten to
accept and ignore the additional argument. By now, none of the original
system is left!
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Récursivité
Un exemple de spécification
fact(0) = 1
fact(n) = n ∗ fact(n − 1)
Programme Scheme récursif
( define ( fact n)
( i f ( zero ? n)
1
( ∗ n ( f a c t ( sub1 n ) ) ) ) )
Récursivité
Evaluation
> ( f a c t 4)
( ∗ 4 ( f a c t 3) )
( ∗ 4 ( ∗ 3 ( f a c t 2) ) )
( ∗ 4 ( ∗ 3 ( ∗ 2 ( f a c t 1) ) ) )
( ∗ 4 ( ∗ 3 ( ∗ 2 ( ∗ 1 ( f a c t 0) ) ) )
24
Une pile est nécessaire pour stocker les valeurs successives
de n qui sont utilisées dans le calcul lors du retour des appels
récursifs.
Récursivité terminale
Spécification
fact-t(0, r ) = r
fact-t(n, r ) = fact-t(n − 1, n ∗ r )
Programme Scheme récursif terminal
( define ( fact−t n r )
( i f ( zero ? n)
r
( f a c t − t ( su b1 n ) ( ∗ n r ) ) ) )
Récursivité terminale
Evaluation
> ( fact−t
( fact−t 3
( fact−t 2
( fact−t 1
( fact−t 0
24
4 1)
4)
12)
24)
24))))
Les valeurs successives de n sont utilisées dans les calculs qui
sont effectués avant les appels récursifs. Il est inutile de les
conserver dans une pile.
Les appels récursifs sont dits terminaux car aucun calcul n’est effectué
après leur retour.
Reconnaı̂tre des fonctions récursives terminales
Les fonctions suivantes sont-elles récursives terminales?
( d e f i n e ( sum1 n )
( i f ( zero ? n)
0
( add1 ( sum1 ( sub1 n ) ) ) ) )
OUI ou NON
( d e f i n e ( sum2 n r )
( i f ( zero ? n)
r
( sum2 ( su b1 n ) (+ n r ) ) ) )
OUI ou NON
( define ( f n)
( cond ( ( z e r o ? n ) 0 )
( ( e v e n ? n ) ( add1 ( f ( sub1 n ) ) ) )
( e l s e ( f ( sub1 n ) ) ) ) )
OUI ou NON
Calculer par exemple : (sum1 3), (sum2 3) et (f 3)
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Les objets Scheme
Les objets
Les atomes
I
Numériques,
I
Booléens,
I
Symboles,
I
Chaı̂nes de caractères
I
Liste vide : ()
Forme quote et symboles
> ( quote P i e r r e )
’ Pierre
> ’ Pierre
’ Pierre
> ( define a ’ Pierre )
> a
’ Pierre
> Pierre
Pierre : undefined ;
> ’( define a ’ Pierre )
’( define a ’ Pierre )
Les paires pointées
I
Constructeur : cons
I
Accesseurs : car, cdr
(argument paire pointée uniquement)
I
Prédicats : pair ?, cons?
I
Abbréviation : cddr, caadr,
..., cadddr, ..., list−ref
Exemple
> ( cons 1 2)
’(1 . 2)
> ( cons ( cons ( cons 1 2) 3) 4)
’ ( ( ( 1 . 2) . 3) . 4)
> ( cons 1 ( cons 2 ( cons 3 4 ) ) )
’(1 2 3 . 4)
> ( c a r ( cons 1 2 ) )
1
> ( cdr ( cons 3 ( cons 1 4 ) )
’(1 . 4)
Affichage des paires pointées
I
(a . (pp)) −→ (a pp) si pp est une paire pointée.
I
(a . ()) −→ (a)
Exemple
> ’(1 . (2 3))
’(1 2 3)
> ( define f ’( define ( f x ) x ))
> f
’( define ( f x ) x )
> ( cons ’ ( 1 2 ) 3 )
’ ( ( 1 2) . 3)
Utilisation de cons, car et cdr
I
I
Quel est le résultat de l’expression (cons 1 (cons (2 (cons 3 ’()))))?
I
’(1 2 3)
I
’(1 . (2 . (3 . ())))
I
’(1 . (2 . (3 . ’())))
I
’(1 2 . 3)
I
’(((1 . 2) . 3) . ())
Soit l’expression (cons 1 (cons (2 (cons 3 ’())))). Quelle expression
permet d’accéder à l’élément 2?
I
(car (cons 1 (cons 2 (cons 3 ’()))))
I
(car (car (cons 1 (cons 2 (cons 3 ’()))))
I
(car (cdr (cons 1 (cons 2 (cons 3 ’()))))
Les listes
Définition récursive des listes Scheme
I
Liste vide : ’() ou null
I
Une paire pointée dont le car est un élément de la liste, et le cdr est
une liste;
Autres types de structures
I
Liste impropre : une liste qui ne se termine pas par la liste vide.
I
Liste circulaire : une chaı̂ne de cons sans fin;
Ces autres types de structures ne sont pas des listes
Définitions formelles
Atome
atome ::= number | symbol | string | ()
Paire pointée
paire-pointée ::= ( objet . objet )
Objet
objet ::= atome | paire-pointée
Liste
liste ::= () | liste
Liste impropre
liste-impropre ::= () | paire-pointée
Fonctions de base sur les listes
I
Prédicats list ?, empty? et null?
I
Prédicats d’égalité : eq?, equal?
I
Fonction de construction : list , (voir aussi list ∗), make−list
I
Fonctions prédéfinies : length, list−ref , list−tail , append,
reverse, member, remove, first , ... tenth, nth, rest, last,
last−pair , take, drop, split−at , take−right, drop−right,
split−at−right , flatten , remove∗, remove−duplicates, range,
shuffle , permutations, remv, remq, memv, memq.
Exemple
> ( append ’ ( 1 ( 2 . 3 ) ) ’ ( 1 ) )
’(1 (2 . 3) 1)
> ( append ’ ( ) ’ ( ) )
’()
> ( append ’ ( 1 ) ’ ( ( ) 2 ) )
’(1 ( ) 2)
I
Fonctions de a-listes : assq, assoc
Construction de listes
Soit les définitions suivantes
( define a 0)
( define b 10)
( define c 3)
Quels sont les résultats des expressions suivantes?
I
(cons a (cons b (cons c ’()))) : (a b c) ou (0 10 3)
I
(list a b c) : (a b c) ou (0 10 3)
I
(list ’a b c) : (a b c) ou (0 10 3) ou (a 10 3) ou (0 b c)
I
’(a b c) : (a b c) ou (0 10 3)
I
(make-list 3 a) : (a a a) ou (0 0 0)
Différencier cons, list et append
Quels sont les résultats des expressions suivantes?
I (cons 1 ’(a b)) : (1 (a b)) ou (1 a b) ou ((1) a b) ou Erreur
I (cons ’(a b) 1) : ((a b) 1) ou (a b 1) ou ((a b) . 1) ou Erreur
I (list 1 ’(a b)) : (1 (a b)) ou (1 a b) ou ((1) a b) ou Erreur
I (list ’(a b) 1) : ((a b) 1) ou (a b 1) ou ((a b) . 1) ou Erreur
I (append 1 ’(a b)) : (1 (a b)) ou (1 a b) ou ((1) a b) ou Erreur
I (append ’(a b) 1) : ((a b) 1) ou (a b 1) ou (a b . 1) ou Erreur
I (append ’(1) ’(a b)) : (1 (a b)) ou (1 a b) ou ((1) a b) ou Erreur
Programmation récursive sur les listes
Définition récursive
I Soit liste vide
I Soit une paire pointée dont le car
est un élément de la liste et le cdr
la suite de la liste, soit la liste
privée de son premier élément.
Somme des éléments d’une liste
I somme-liste(’()) = 0
I somme-liste(l) = car(l) +
somme(cdr(l))
Spécification récursive d’un
calcul
I Si la liste est vide, calculer le
résultat correspondant.
I Sinon, exprimer le calcul en
fonction de l’elément courant et
du résultat d’un appel récursif sur
la liste privée de son premier
élément.
Exemple
( d e f i n e ( somme l )
( i f ( empty ? l )
0
(+ ( c a r l )
( somme ( c d r l ) ) ) ) )
Programmation récursive sur les arbres
Définition récursive
I Soit un atome
I Soit une paire pointée admettant pour car et pour cdr un objet lisp (un
atome ou une paire pointée)
Spécification récursive d’un calcul
I Si l’arbre est vide, calculer le résultat correspondant.
I Sinon, exprimer le calcul en fonction du fils gauche (le car ), et du fils
droit (le cdr ).
Exemple
( define ( nb−feuilles a)
( cond ( ( n u l l ? a ) 0 )
( ( not ( p a i r ? a ) ) 1 )
( e l s e (+ ( n b − f e u i l l e s ( c a r a ) )
( n b − f e u i l l e s ( cdr a ) ) ) ) ) )
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Système de type
Classification des valeurs en ensembles appelés types de manière à
garantir la correction de certains programmes.
Any
Base types
Function types
Complex types
Number
Boolean
String
Char
Symbol
Procedure
Struct
U (Union)
Polymorphic types
Complex
Real
Float
Integer
t −> t éq. à −> t t
Pairof s t
Listof t
Vectorof t
Styles de typage
Styles de typage
I
le typage dynamique : déterminé pendant l’exécution par le runtime, il
ne nécessite aucune intervention du programmeur;
I
le typage statique : fixé avant l’exécution par le compilateur, il est soit
inféré automatiquement, soit indiqué par des annotations dans le code.
Racket définit en fait plusieurs langages :
I
En Racket classique, le typage est dynamique;
I
En Typed/Racket, le typage est dynamique mais autorise des
annotations statiques vérifiées par le compilateur.
Comment typer ?
Afin d’annoter une valeur hvali par un type htypi, il suffit d’écrire avant
la définition de hvali :
I
(: hvali htypi)
Ex :
(: int exm Integer )
Les types primitifs contiennent en particulier : Number, Integer , Float,
Char, String, . . .
Les types des fonctions sont écrits à l’aide d’une des syntaxes suivantes :
I
(harg1 i harg2 i ... hargn i −> hresi)
Ex :
(Number Number −> Boolean)
I
(−> harg1 i harg2 i ... hargn i hresi)
Ex :
(−> Number Number Boolean)
Exemple
#l a n g t y p e d / r a c k e t
( : num−fun ( Number −> Number ) )
( d e f i n e ( num−fun n ) ( add1 n ) )
( : p r i n t − t y p e num−fun )
;
;
;
;
;
;
;
;
Choice of the language
Type a n n o t a t i o n o f num−fun
D e f i n i t i o n o f num−fun
(−> Number Number )
Intérêts du typage
I
Détection d’erreurs de type : passer une valeur de type String à une
fonction Int −> Int est incohérent.
Exemple
( : num−fun ( Number −> Number ) )
( d e f i n e ( num−fun n ) ( add1 n ) )
( num−fun ” abc ” )
; ; −> Type C h e c k e r e r r o r : t y p e mismatch
I
Compatibilités de type : passer une valeur de type Integer à une fonction
Number −> Number est cohérent car un Integer est aussi un Number.
Exemple
(: intg Integer )
( d e f i n e i n t g 3)
( num−fun i n t g )
I
; ; Type a n n o t a t i o n o f i n t g
; ; −> 4
Optimisations : le compilateur peut écrire du code dédié à des types
particuliers (exemple : nombre flottants et instructions FPU).
Exemples
Exemple
( : g r e a t e r − t h a n ( Number Number −> B o o l e a n ) )
( define ( greater−than x y )
(> x y ) )
; ; Type e r r o r : Number c a n n o t be compared
En effet, un Number peut aussi être un Complex, donc non comparable.
Exemple
( : p l u s ( Number Number −> Number ) )
( d e f i n e ( p l u s x y ) (+ x y ) )
( : g r e a t e r − t h a n ( R e a l R e a l −> B o o l e a n ) )
( d e f i n e ( g r e a t e r − t h a n x y ) (> x y ) )
( greater−than ( p l u s 3 4) 5)
; ; Type e r r o r : Number c a n n o t be compared
En effet, plus renvoie un Number, qui potentiellement n’est pas un Real.
Remarque : en réalité, le type de l’opérateur + est générique.
Définir ses propres types
Le code suivant définit un Struct représentant des points du plan :
( struct : point ( [ x : Real ] [ y : Real ] ) )
( : d i s t a n c e ( p o i n t p o i n t −> R e a l ) )
( d e f i n e ( d i s t a n c e p1 p2 )
( s q r t (+ ( s q r (− ( p o i n t − x p2 ) ( p o i n t − x p1 ) ) )
( s q r (− ( p o i n t − y p2 ) ( p o i n t − y p1 ) ) ) ) ) )
Cette construction définit en même temps les fonctions suivantes :
I
Un constructeur point permettant de construire des instances
comme par exemple (point 3 4).
I
Deux accesseurs point−x et point−y permettant d’accéder aux
champs de la structure.
Types inductifs : les listes
Définition récursive des listes en Scheme (Rappel)
I
Soit la liste vide : null ou ’()
I
Soit une paire (car , cdr) où car est un élément de la liste et cdr est
une liste.
La définition de type pour les listes en Racket :
; ; A L i s t i s e i t h e r a P a i r o r Empty
( d e f i n e − t y p e ( L i s t a ) (U ( P a i r a ) Empty ) )
; ; A P a i r i s a s t r u c t w i t h c a r and c d r , and Empty i s empty
( struct : ( a ) Pair ( [ car : a ] [ cdr : ( L i s t a ) ] ) )
( s t r u c t : Empty ( ) )
Remarque : le type pour les listes est un type polymorphe.
(: a l i s t ( List Integer ))
( d e f i n e a l i s t ( P a i r 1 ( P a i r 2 ( P a i r 3 ( Empty ) ) ) ) )
Reconnaissance de motif
La forme match
( match t
[ hpat1 i res1 ]
[ hpat2 i res2 ]
...
[ hpatn i resn ]
default ]
[
)
La reconnaissance de motif ou pattern-matching :
I
compare l’expression t à chacun des motifs hpatk i
I
et renvoie le résultat associé au premier indice
pour lequel t correspond.
Les motifs peuvent introduire des liaisons utilisées
dans le résultat.
Exemple pour calculer la longueur d’une liste :
( define ( list−length l )
( match l
[ ( Empty )
0]
[ ( Pair x xs )
( add1 ( l i s t − l e n g t h x s ) ) ]
))
; ; Match Empty s t r u c t
; ; Match P a i r s t r u c t
; ; and b i n d s x and x s
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Fonctionnelles
Higher-order functions
I
Fonction anonyme : forme lambda
I
Fonctions en arguments
I
Fonctions en retour
La forme lambda
( lambda ( hp1 i hp2 i . . . hpn i )
hei )
Exemple
> ( lambda ( x y )
( / (+ x y ) 2 ) )
#<p r o c e d u r e >
> ( ( lambda ( x ) ( s ub1 x ) ) 1 )
0
Fonctions en arguments
I
Appartenance : (memf hproci hlsti)
I
Filtrage : ( filter hpredi hlsti)
I
Liste d’associations : ( assoc hv i hlsti )
( assoc hv i hlsti hpredi )
( a s s f hproci hlsti )
I
Constructeur : ( build−list hni hproci)
I
Itération sur des listes : apply, map, andmap, ormap, foldl , foldr
Exemple
> (map su b1 ’ ( 1 2
’(0 1 2 3 4)
> (map cons ’ ( 1 2
’ ( ( 1 . a ) (2 . b )
> (map ( lambda ( x )
’((2) (3) (4))
3 4 5))
3 4) ’ ( a b c d ) )
(3 . c ) (4 . d ) )
( l i s t ( add1 x ) ) )
’(1 2 3))
Fonctionnement de la forme apply
Quels sont les résultats des expressions suivantes?
> ( app ly ∗ ’ ( 1 2 3 ) ’ ( 1 2 ) )
12 ou erreur
> ( app ly ∗ 1 2 3 ’ ( 1 2 ) )
12 ou erreur
> ( app ly ∗ 1 2 3 1 2 )
12 ou erreur
> ( app ly ∗ 1 2 3 1 2 ’ ( ) )
12 ou erreur
> ( app ly and ’ ( \#t \#f \#t ) )
#f ou erreur
> ( app ly map l i s t
’((1 2) (2 3) (3 4))
’ ( ( 1 2 3) (2 3 4 ) ) )
ou ’((1 2 3) (2 3 4))
ou erreur
Exemple de fonction n-aire avec apply
La fonction ou n-aire
( d e f i n e ( ou . l )
( cond ( ( n u l l ? l ) #f )
(( car l ))
( e l s e ( a pp ly ou ( c d r l ) ) ) ) )
Questions
I
(apply ou ’(#f #t)) : #t ou erreur
I
(ou #f #t #f (print #f)) : #t ou #f#t ou #t#f
Fonctions en retour de fonctions
Opérations de composition
I
Fonctions unaires : (compose1 hproc1 i hproc2 i ... hprocn i)
I
Arité quelconque : (compose hproc1 i hproc2 i ...hprocn i)
Le nombre de résultats de hproci i doit correspondre à l’arité de hproci−1 i
Exemple
> ( ( compose1 s q r t add1 ) 1 ) ; ( s q r t ( add1 1 ) )
1.414213562
> ( d e f i n e ( 2 r x y ) ( v a l u e s (+ x y ) (− x y ) ) )
> ( ( compose l i s t 2 r ) 1 2 ) ; ( l i s t ( 2 r 1 2 ) )
’ ( 3 −1)
Questions
I ((compose - sqr sub1) 3) : -4 ou 8
I ((compose + 2r) 1 2) : 2 ou 3
I ((compose (lambda(x) (apply * x)) (lambda(x) (map sub1 x))) ’(1 2 3)) :
Fonctions en retour de fonctions
Opérations sur l’arité
I
Arité d’une fonction : (procedure−arity hproci)
I
Réduction d’arité : (procedure−reduce−arity hproci harity i)
I
Currification : (curry hproci)
Exemple
> ( define
> (++ 1 )
2
> (( curry
(1 2)
> ( define
> ( define
++ ( ( c u r r y +) 1 ) )
l i s t ) 1) 2)
make−map ( c u r r y map ) )
map−sub1−sqr ( compose ( make−map sub1 )
( make−map s q r ) ) )
> ( map−sub1−sqr ’ ( 1 2 3 4 5 ) )
’ ( 0 3 8 15 2 4 )
Exemple de curryfication
On va curryfier la fonction filter de la bibliothèque pour la spécialiser sur
le prédicat even?
> ( f i l t e r even ? ’(1 2 3 4))
’(2 4)
> ( define f i l t e r − e v e n ( ( c u r r y f i l t e r ) even ? ))
> ( f i l t e r − e v e n ’(1 2 3 4))
’(2 4)
Cela peut permettre de l’utiliser dans une fonctionnelle de liste comme
map par exemple.
Exemple
> (map f i l t e r − e v e n
’ ( ( 2 4) (6 2 4))
’ ( ( 1 2 3 4 ) ( 3 6 2 4 23 1 ) ) )
Fonctionnement de compose, map et apply
Quels sont les résultats des expressions suivantes?
> (map ( ( c u r r y cons ) 1 ) ’ ( a b c ) )
’((1 . a) (1 . b) (1 . c))
> ( ( ( c u r r y a pp ly ) ∗ ) ’ ( 1 2 3 ) )
’(1 a b c)
erreur
6
> ( ( ( c u r r y map) ∗ ) ’ ( 1 2 3 ) )
’(1 2 3)
erreur
6
’(1 2 3)
erreur
> ( ( compose ( ( c u r r y a pp ly ) ∗ )
( ( c u r r y map) sub1 ) )
’(1 2 3))
0
erreur
Programmation de fonctionnelles
Composition de fonctions
f : A −→ B
g : B −→ C
La composée de f par g est la fonction h : A −→ C telle que h(x) = g (f (x)).
Elle est notée h = gof .
( define (o g f )
( lambda ( x )
(g ( f x ))))
Exemple
> ( o s q r sub1 )
#<p r o c e d u r e >
> ( ( o c a r r e sub1 ) 2 )
1
> ( d e f i n e c a r r e − 1 ( o c a r r e sub1 ) )
> ( carre−1 2)
1
Ecriture de fonctionnelles
Quelle est la version la plus efficace?
I
(define (carre-1 x) ((o sqr sub1) x))
I
(define carre-1 (o sqr sub1))
Fermetures
Soit la session suivante :
>
>
>
>
( define
( define
( define
( define
( g x ) (+ x 1 0 ) ) )
( f x) (∗ 2 x ))
( carre x) (∗ x x ))
c a r r e − 1 ( o c a r r e sub1 ) )
Supposons que la fonction carre-1 soit représentée par son code :
( lambda ( x ) ( g ( f x ) ) )
Quel est le résultat de l’expression suivante : 1 ou 14 ?
> ( carre−1 2)
Notion de fermeture (closure)
Définitions
I
L’environnement lexical d’une fonction est l’environnement dans
lequel elle est définie.
I
Une fermeture (closure en anglais) est la représentation d’une
fonction sous forme d’un couple associant l’environnement lexical et
le code de la fonction.
I
En Scheme les fonctions sont représentées par des fermetures pour
conserver leur environnement de définition contenant des références
éventuelles (ce n’est pas le cas par exemple du langage emacs-lisp).
I
Les fermetures peuvent être utilisées pour représenter des états, par
modification de l’environnement (voir chapitre suivant).
Emacs-lisp
Emacs-lisp n’a pas de fermetures, les fonctions ne sont représentées que par
leur code (sans l’environnement lexical). Soient les fonctions :
( defun o ( g f ) ( lambda ( x ) ( f u n c a l l g ( f u n c a l l f x ) ) ) )
( defun c a r r e ( x ) ( ∗ x x ) )
Lors de l’application :
( f u n c a l l ( o #’ c a r r e #’1−) 2 )
On obtient :
Debugger e n t e r e d − − L i s p e r r o r : ( v o i d − v a r i a b l e g )
( funcall g ( funcall f x ))
( lambda ( x ) ( f u n c a l l g ( f u n c a l l f x ) ) ) ( 2 )
f u n c a l l ( ( lambda ( x ) ( f u n c a l l g ( f u n c a l l f x ) ) ) 2 )
e v a l ( ( f u n c a l l ( o ( f u n c t i o n c a r r e ) ( f u n c t i o n 1−)) 2 ) n i l )
Programmation de fonctionnelles récursives
Première ébauche d’écriture d’une fonction de composition d’une liste de
fonctions : ici, tout le calcul est gelé par la λ-expression. Il s’éxecutera
entièrement au moment de l’application de la fonction résultat.
Exemple
( d e f i n e ( bad1 . l )
( lambda ( x )
( if ( null ? l )
x
( ( c a r l ) ( ( a p p l y bad1 ( c d r l ) ) x ) ) ) ) )
> ( d e f i n e add2 ( bad1 add1 add1 ) )
( lambda ( x ) ; l e r e s u l t a t e s t l a l a m b d a − e x p r e s s i o n
( i f ( n u l l ? ’ ( add1 add1 ) )
x
( add1 ( ( bad1 add1 ) x ) ) ) )
Deuxième ébauche d’écriture : bad2
On sort le if de la λ-expression afin qu’il s’éxécute lors de l’application de
la fonctionnelle.
( d e f i n e ( bad2 . l )
( i f ( null ? l )
identity
( lambda ( x )
( ( c a r l ) ( ( a pp ly bad2 ( c d r l ) ) x ) ) ) ) )
Application de bad2 :
> ( d e f i n e add2 ( bad2 add1 add1 ) )
( i f ( n u l l ? ’ ( add1 add1 ) )
identity
( lambda ( x )
( add1 ( ( bad2 add1 ) x ) ) ) )
Étape suivante : λ ou if ou récursion ou forme normale
Application de bad2 (1)
(if (null? ’(add1 add1))
identity
(lambda(x)
(add1 ((bad2 add1) x))))
β
−→
( lambda ( x )
( add1 ( ( bad2 add1 ) x ) ) )
Étape suivante : λ ou if ou récursion ou forme normale
Solution récursive terminale
Pour être sûr de mettre l’appel récursif dans la fonctionnelle, il faut
rendre celle-ci récursive terminale.
( define (o f . l )
( i f ( null ? l )
f
( a pp ly o ( lambda ( x ) ( ( c a r l ) ( f x ) ) )
( cdr l ) ) ) )
Lors de l’application de la fonctionnelle o, tous les calculs s’effectuent,
sauf ceux qui nécessitent de connaı̂tre l’argument de la fonction résultat.
Conclusion
Règles d’écriture
I
L’appel récursif doit être effectué par la fonctionnelle plutôt que par
la fonction résultat. De cette façon, on effectue la boucle une seule
fois au moment de la construction, sinon, la boucle est effectuée à
chaque application de la fonction résultat.
I
Pour ne pas geler l’appel récursif de la fonctionnelle, il faut qu’il soit
extérieur à toute fermeture (λ-expression), ce qui implique de
constuire les fermetures en arguments plutôt qu’en valeurs de retour
d’appels récursifs, et donc de rendre les fonctionnelles récursives
terminales.
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Formes impératives
Références
Une référence est un objet correspondant à une adresse
mémoire et dont l’indirection est faite automatiquement dans
toute situation où une valeur est requise. L’adresse associée à
une référence n’est pas directement manipulable en tant que
telle (il n’existe pas d’opérations pour le programmeur sur les
références)
I
Un symbole est lié à une référence, correspondant à un atome ou
une paire pointée.
I
L’évaluation d’un symbole renvoie une référence vers sa valeur.
I
La référence est utilisée partout où la valeur n’est pas requise.
I
On trouve des références dans d’autres langages : Java, C++.
Passage d’arguments
Soit f une fonction, soient p1, p2, ... pn ses paramètres formels. Soit
l’application :
(f a1 a2 ... an)
Soient r1, r2, ..., rn les références vers les résultats des évaluations
respectives des arguments a1, a2, ..., an.
Lors de l’application, un environnement local est construit. Il est
constitué des liaisons entre les paramètres formels pi de la fonction f et
les références ri des arguments de l’application.
((p1 . r 1)(p2 . r 2)...(pn . rn))
Les références r1, r2, ..., rn sont utilisées comme des valeurs à travers les
symboles p1, p2, ..., pn, les indirections étant effectuées
automatiquement. Ainsi, il est impossible de modifier un paramètre pi,
car la modification reste locale à cet environnement.
L’affectation
La forme set!
(set! hidi hei)
I
La référence associée à l’identificateur hidi est remplacée par la
référence du résultat de l’évaluation de l’expression hei.
I
La valeur de retour de l’affectation est la valeur # < void > que la
fonction read n’affiche pas. La procédure void rend ce même
résultat en prenant un nombre quelconque d’arguments.
Modification de paires pointées
On ne peut pas modifier les paires pointées de base dans la norme
scheme. En Racket, il faut utiliser le paquetage mpair
Exemple
> ( d e f i n e mp ( mcons 1 2 ) )
> ( set−mcar ! mp 2 )
> mp
( mcons 2 2 )
Modification de paramètres
On se donne la session suivante.
( define ( incrementer x )
( s e t ! x ( add1 x ) ) )
> ( i n c r e m e n t e r 2)
> ( define i 0)
> ( incrementer i )
Quel est le résultat de cette expression? 0 ou 1
> i
Blocs d’expressions
Si x est plus petit que y, on veut échanger x et y et renvoyer x, sinon, on
veut renvoyer y. Cette expression est-elle correcte? oui non
( l e t ( ( tmp 0 ) )
( i f (< x y )
( s e t ! tmp x )
( set ! x y )
( s e t ! y tmp )
x
y ))
Blocs d’expressions
Certaines expressions pouvant effectuer des effets de bord, il devient
possible de les mettre en séquence. Certaines formes, telles le if
nécessitent d’utiliser une forme spéciale de mise en séquence.
La forme begin
(begin e1 e2 ... en)
I
Chaque expression ei est évaluée selon son ordre d’apparition.
I
Le résultat de l’évaluation de la séquence est celui de la dernière.
I
Les valeurs des évaluations des expressions précédentes sont
perdues.
I
Il existe une forme begin0 qui renvoie le résultat de la première
expression de la séquence.
Fermetures et affectations : en Common Lisp
On peut utiliser les fermetures pour modéliser des états.
Générateurs en Common Lisp
( let (( i 0))
( defun g e n − e n t i e r ( )
( s e t f i (1+ i ) ) ) )
Exemple
∗ ( gen−entier )
1
∗ ( gen−entier )
2
∗ ( gen−entier )
3
Fermetures et affectations : et en Scheme?
I
Quel est le résultat de la session suivante? 1 ou Erreur
( let (( x 1))
( define ( f )
x)
( f ))
1
> (f)
I
Quel est le résultat de la session suivante? 1 ou 0 ou erreur
( d e f i n e ( make−f )
( let (( x 1))
( lambda ( ) x ) ) )
> ( d e f i n e e ( make−f ) )
> ( define x 0)
> (e)
Fermetures et affectations : générateurs en Scheme
Exemple
( d e f i n e ( make−int−gen n )
( let (( i n ))
( lambda ( )
( s e t ! i ( add1 i ) )
i )))
> ( d e f i n e i n t − g e n 0 ( make−int−gen −1))
> ( int−gen0 )
0
> ( int−gen0 )
1
> ( int−gen0 )
2
Les mémo-fonctions (memo functions, memoization)
La technique des mémo-fonctions est utilisée pour optimiser le calcul des
fonctions, en mémorisant des résultats d’appels coûteux.
Suite de Fibonacci
( d e f i n e ( make−memo−fib ) ; C r e a t i o n de l a f e r m e t u r e
; I n i t i a l i s a t i o n de l a t a b l e d a n s l ’ e n v i r o n n e m e n t l e x i c a l
( l e t ( ( memo−table ’ ( ( 1 . 1 ) ( 2 . 1 ) ) ) )
( d e f i n e ( memo−fib n ) ; d e f i n i t i o n de l a f o n c t i o n
; Recherche dans l a t a b l e
( l e t ( ( computed−value ( a s s o c n memo−table ) ) )
( i f computed−value
( c d r computed−value ) ; l a v a l e u r e s t t r o u v e e
; ; La v a l e u r e s t c a l c u l e e e t s t o c k e e
( l e t ( ( new−value (+ ( memo−fib ( s u b 1 n ) ) ; c a l c u l
( memo−fib (− n 2 ) ) ) ) )
( s e t ! memo−table ; s t o c k a g e
( c o n s ( c o n s n new−value )
memo−table ) )
new−value ) ) ) ) ; r e t o u r de l a v a l e u r
memo−fib ) ) ; r e t o u r de l a f o n c t i o n
Les mémo-fonctions : utilisation
Comme pour les générateurs, il faut créer la fermeture par une première
application de la fonctionnelle.
Exemple
> ( d e f i n e memo−fib ( make−memo−fib ) )
> ( memo−fib 5 )
5
> ( memo−fib 8 )
21
> ( memo−fib 1 0 0 )
354224848179261915075
Portée et transparence référentielle
Les deux applications ( f 3) sont effectuées dans deux environnements différents. Le
premier lie i avec 0 et le second lie i avec 2.
Portée lexicale : Scheme
Les résultats des deux applications sont-ils les mêmes? oui ou non
> (define i 0)
> (define (f x) (* x i))
> (f 3)
> (let ((i 2)) (f 3) )
Portée dynamique : emacs-lisp
Les résultats des deux applications sont-ils les mêmes? oui ou non
(defvar i 0)
(defun f(x) (* x i))
(let ((i 2)) (f 3) )
(f 3)
Portée et transparence référentielle
Les deux applications ( f 3) sont effectuées dans deux environnements différents. Le
premier lie i avec 0 et le second lie i avec 2.
Portée lexicale : Scheme
Les résultats des deux applications sont les mêmes : transparence référentielle.
> (define i 0)
> (define (f x) (* x i))
> (f 3) -> 0
> (let ((i 2)) (f 3) ) ->
0
Portée dynamique : emacs-lisp
Les résultats dépendent de l’environnement : pas de transparence référentielle.
(defvar i 0)
(defun f(x) (* x i))
(let ((i 2)) (f 3) ) -> 6
(f 3) -> 0
Portée : formulation fonctionnelle
Méthode
On utilise la formulation fonctionnelle du let pour étudier le phénomène
de portée en calculant le résultat sur les exemples précédents..
(let ((j 0))
(let ((g (lambda(x)
(* x j))))
(let ((j 3))
(g 3))))
((lambda(j)
((lambda(g)
((lambda(j)
(g 3))
3))
(lambda(x) (* x j))))
0)
Portée : formulation fonctionnelle
((lambda(j)
((lambda(g)
((lambda(j)
(g 3))
3))
(lambda(x) (* x j))))
0)
->
((lambda(g)
((lambda(j)
(g 3))
3))
(lambda(x) (* x 0)))
Portée : formulation fonctionnelle
((lambda(g)
((lambda(j)
(g 3))
3))
(lambda(x) (* x 0)))
->
((lambda(j)
((lambda(x) (* x 0)) 3))
3)
Portée : formulation fonctionnelle
((lambda(j)
((lambda(x) (* x 0)) 3))
3)
->
((lambda(x) (* x 0)) 3)
Portée : formulation fonctionnelle
((lambda(x) (* x 0)) 3)
->
(* 3 0)
->
0
Plan
Objectifs pédagogiques
Introduction
Types et constructions de base du langage
Environnements
Récursivité
Les listes
Types
Les fonctionnelles
Les formes impératives
Les macroexpansions
Macroexpansions
Définition
( defin e−s y nt a x−r u le hpatterni htemplatei )
I
hpatterni : (hnomi-hmacroi hp1 i ...)
I
hpi i : variables de la macro
I
Remplacement des variables dans le template
I
Le résultat est une forme
I
Évaluation de la forme dans l’environnement d’appel
Macroexpansions
Exemple
( define−syntax−rule ( i f n o t t e s t then e l s e )
( i f ( not t e s t )
then
else ))
> ( d ef i ne x 1)
> ( expand−once # ’( i f n o t (= 1 2 ) 0 x ) )
#<s y n t a x : 8 : 1 7 ( i f ( not (= 1 2 ) ) 0 x)>
> ( syntax−>datum ( expand−once # ’( i f n o t (= 1 2 ) 0 x ) ) )
’ ( i f ( not (= 1 2 ) ) 0 x )
> ( i f n o t (= 1 2 ) 0 x )
0
Macroexpansions
Citation
I
Quote : ’
I
Backquote : ‘
I
Virgule : ,
I
Arobase : @
Exemple
> ( define l ’(1 2 3))
> ‘(1 , l )
(1 (1 2 3))
> ‘(+ , l )
(+ 1 2 3 )
Téléchargement