Programmation fonctionnelle en langage Scheme - Support PG104 -
Transcription
Programmation fonctionnelle en langage Scheme - Support PG104 -
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 )