Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Approches Fonctionnelles de la Programmation Introduction Typage Résumé Didier Verna [email protected] http://www.lrde.epita.fr/˜didier 1/34 Table des matières Programmation Fonctionnelle Didier Verna E PITA 1 Un paradigme de programmation 2 La fonction : un objet de 1re classe 3 Programmation fonctionnelle pure / impure 4 Évaluation stricte / lazy 5 Les formes de typage 6 Résumé Paradigme 1er ordre Pureté Évaluation Typage Résumé 2/34 Un paradigme de programmation « Quoi faire » plutôt que « Comment faire » Programmation Fonctionnelle Didier Verna E PITA Un paradigme ? Affecte l’expressivité d’un langage Paradigme Affecte la manière de penser dans un langage 1er ordre Le concept de paradigme est poreux. . . Pureté Évaluation Lequel ? Typage Expressions Résumé Définitions (expressions nommées) Évaluations (de définitions ou d’expressions) 4/34 De l’impératif au fonctionnel « La somme des carrés des entiers entre 1 et N » Programmation Fonctionnelle Didier Verna E PITA Paradigme C (impératif) Lisp i n t ssq ( i n t n ) { int i = 1 , a = 0; ( defun ssq ( n ) ( i f (= n 1 ) 1 (+ (∗ n n ) ( ssq (1− n ) ) ) ) ) 1er ordre while ( i <= n ) { a += i ∗ i ; i += 1 ; } Pureté Évaluation Typage Résumé return a ; } C (récursif) i n t ssq ( i n t n ) { i f ( n == 1 ) return 1; else r e t u r n n∗n + ssq ( n −1); } 5/34 Haskell ssq : : I n t −> I n t ssq 1 = 1 ssq n = n∗n + ssq ( n−1) Clarté Concision L’impératif vu à l’envers « La racine carrée de la somme des carrés de a et de b » Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre C (impératif) Haskell f l o a t hypo ( f l o a t a , f l o a t b ) { f l o a t a2 = a∗a ; f l o a t b2 = b∗b ; f l o a t s = a2 + b2 ; hypo : : F l o a t −> F l o a t −> F l o a t hypo a b = s q r t ( ( a∗a ) + ( b∗b ) ) Pureté Évaluation return s q r t ( s ) ; } Typage Résumé C (moins impératif) f l o a t hypo ( f l o a t a , f l o a t b ) { r e t u r n s q r t ( a∗a + b∗b ) ; } 6/34 Lisp ( defun hypo ( a b ) ( s q r t (+ (∗ a a ) (∗ b b ) ) ) ) Pour être tout à fait honnête... Haskell (100% préfixe) hypo : : F l o a t −> F l o a t −> F l o a t hypo a b = s q r t ( ( + ) ( ( ∗ ) a a ) ( ( ∗ ) b b ) ) La fonction : un objet de 1re classe Christopher Strachey (1916-1975) Programmation Fonctionnelle Didier Verna E PITA . . . du 1er ordre, d’ordre supérieur. . . stockage (variables) Paradigme agrégation (structures) 1er ordre argument de fonction Pureté Évaluation retour de fonction Typage manipulation anonyme Résumé construction dynamique ... Plus d’expressivité (clarté, concision etc.) 8/34 Nommage par une variable Que peut-on manipuler ? Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Typage C Lisp /∗ 3 + 4 ∗/ ; ; (+ 3 4 ) int plus ( int a , int b ) { return a + b ; } /∗ p l u s ( 3 , 4 ) ; ∗/ ( setf func # ’+) ; ; ( f u n c a l l func 3 4) ; ; ( f u n c a l l # ’+ 3 4 ) ( s e t f ( symbol−function ’ f u n c ) # ’ + ) ; ; ( func 3 4) typedef i n t (∗ f o o _ f ) ( i n t , i n t ) ; foo_f func = plus ; /∗ (∗ f u n c ) ( 3 , 4 ) ; ∗/ Scheme Résumé pointeurs sur fonction ; ; (+ 3 4 ) ( define f u n c +) ; ; ( func 3 4) Haskell −− 3 + 4 −− ( + ) 3 4 f u n c : : I n t −> I n t −> I n t func = ( + ) −− f u n c 3 4 9/34 fonctions elles-mêmes Arguments fonctionnels Mapping et folding Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Deux Archétypes du passage d’argument fonctionnel : mapping : traiter individuellement les éléments d’une liste par une fonction. Lisp Haskell ( mapcar # ’ s q r t ’ ( 1 2 3 4 5 ) ) map s q r t [ 1 . . 5 ] Évaluation Typage Résumé 10/34 folding : combiner les éléments d’une liste par une fonction. Lisp Haskell ( reduce # ’+ ’ ( 1 2 3 4 5 ) ) foldr1 (+) [ 1 . . 5 ] sum [ 1 . . 5 ] Application « La somme des carrés des entiers entre 1 et N » II, le retour Programmation Fonctionnelle Autre vision algorithmique (non récursive) : « sommer la liste des carrés de 1 à N » Didier Verna E PITA Paradigme Lisp Haskell 1er ordre ( defun sq ( x ) (∗ x x ) ) Pureté sq : : I n t −> I n t sq x = x ∗ x ( defun i n t l i s t ( n ) ( i f (= n 1 ) ( l i s t 1) ( cons n ( i n t l i s t (1− n ) ) ) ) ) ssq : : I n t −> I n t ssq n = sum (map sq [ 1 . . n ] ) Évaluation Typage Résumé ( defun ssq ( n ) ( reduce # ’+ ( mapcar # ’ sq ( i n t l i s t n ) ) ) ) Gain en abstraction : I I 11/34 définition de ssq encore plus concise mise en évidence d’abstraction supplémentaire Fonctions anonymes Des littéraux comme les autres. . . Programmation Fonctionnelle Didier Verna E PITA Paradigme Possibilité de ne pas nommer les fonctions : Lisp Haskell ( lambda ( x ) (∗ 2 x ) ) \ x −> 2 ∗ x 1er ordre Utilisation directe (littérale) : au même titre que les int, les chaînes de caractères etc. Pureté Évaluation Typage Résumé 12/34 Lisp Haskell ( ( lambda ( x ) (∗ 2 x ) ) 4 ) ( \ x −> 2 ∗ x ) 4 Application « La somme des carrés des entiers entre 1 et N » III, le retour de la vengeance Programmation Fonctionnelle Économie de nommage : éviter le surpeuplement de petites fonctions. Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Typage Résumé 13/34 Lisp Haskell ( defun ssq ( n ) ( reduce # ’+ ( mapcar ; ; # ’ sq ( lambda ( x ) (∗ x x ) ) ( intlist n)))) ssq : : I n t −> I n t ssq n = sum (map −− sq ( \ x −> x ∗ x ) [1..n]) Retours fonctionnels Mais surtout, construction de fonctions à la volée Programmation Fonctionnelle Didier Verna E PITA Paradigme Le retour fonctionnel est inutile. . . C Lisp int plus ( int a , int b ) ; i n t minus ( i n t a , i n t b ) ; ( defun +/− ( which−one ) ( i f (= which−one 0 ) # ’+ # ’−)) 1er ordre Pureté Évaluation Typage Résumé typedef i n t (∗ f o o _ f ) ( i n t , i n t ) ; f o o _ f p_or_m ( i n t which_one ) { r e t u r n ( which_one == 0 ) ? p l u s : minus ; } ; ; ( f u n c a l l (+/− 0 ) 4 2 ) Haskell /∗ (∗ p_or_m ( 0 ) ) ( 4 , 2 ) ; ∗/ p_or_m : : I n t −> ( I n t −> I n t −> I n t ) p_or_m 0 = ( + ) p_or_m _ = ( −) −− ( p_or_m 0 ) 4 2 14/34 Retours fonctionnels Mais surtout, construction de fonctions à la volée Programmation Fonctionnelle . . . sans construction au vol : D’où l’importance des fonctions anonymes. . . Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Lisp Haskell ( defun adder ( n ) ( lambda ( x ) (+ x n ) ) ) adder : : I n t −> ( I n t −> I n t ) adder n = \ x −> x + n ; ; ( f u n c a l l ( adder 3 ) 1 ) −− ( adder 3 ) 1 Lisp Haskell ( defun t w i c e ( f ) ( lambda ( x ) ( funcall f ( funcall f x ) ) ) ) t w i c e : : ( a −> a ) −> ( a −> a ) twice f = f . f Typage Résumé −− ( t w i c e s q r t ) 16 ; ; ( f u n c a l l ( t w i c e # ’ s q r t ) 16) 15/34 Application « La somme des carrés des entiers entre 1 et N » IV, Apocalypse Programmation Fonctionnelle Gain en abstraction considérable : Il devient immédiat de définir d’autres combinaisons utiles. Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Typage Lisp Haskell ( defun foldmap ( f m) ( lambda ( x ) ( reduce f ( mapcar m x ) ) ) ) foldmap : : ( c −> c −> c ) −> ( c −> c ) −> ( [ c ] −> c ) foldmap f m = f o l d r 1 f . (map m) ( defun ssq ( n ) ( funcall ( foldmap # ’+ ( lambda ( x ) (∗ x x ) ) ) ( intlist n))) ssq : : I n t −> I n t ssq n = foldmap ( + ) ( \ x −> x∗x ) [ 1 . . n ] Résumé 16/34 Pseudo-1er ordre dans les langages impératifs On fait ce qu’on peut. . . Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Typage Résumé 19/34 Les structures de contrôle impératives. . . sont des formes fixes de fonctions d’ordre supérieur. if (expression) { /* LAMBDA PROCEDURE ! */ /* blah blah ... */ } else { /* LAMBDA PROCEDURE ! */ /* blah blah ... */ } Programmation fonctionnelle pure La fonction au sens mathématique Programmation Fonctionnelle Didier Verna E PITA Paradigme ( 1 si x = 1, ssq(x) = 2 x + ssq(x − 1) sinon. Haskell ssq : : I n t −> I n t ssq 1 = 1 ssq n = n∗n + ssq ( n−1) Fonction : 1er ordre Pureté Évaluation Typage Résumé Impératif : procédure. Ensemble de calculs à effets de bords avec éventuellement retour d’une valeur. Fonctionnel pur : calcul d’une valeur de sortie (retour) en fonction de valeurs d’entrée (arguments). Variable : Impératif : représente un stockage d’information qui varie au cours du temps (« mutation »). Fonctionnel pur : constante. Représente une valeur inconnue ou arbitraire. Chaque occurrence est interprétée de la même manière. 21/34 Intérêts de la pureté Pureté ⇐⇒ Sureté Programmation Fonctionnelle Didier Verna E PITA Parallélisme I Cf. Erlang Sémantique locale aux fonctions Paradigme 1er ordre Pureté Évaluation Typage Résumé 22/34 I Tests locaux / Bugs locaux Preuve de programme Preuves formelles Induction mathématique Programmation Fonctionnelle Didier Verna E PITA Paradigme « Prouvez-moi (s’il vous plaît) que ∀N, ssq(N) > 0 » Fonctionnel pur : Impératif : Haskell C ssq : : I n t −> I n t ssq 1 = 1 ssq n = n∗n + ssq ( n−1) i n t ssq ( i n t n ) { int i = 1 , a = 0; 1er ordre Pureté Évaluation Typage Résumé while ( i <= n ) { a += i ∗ i ; i += 1 ; } C’est vrai au rang 1 Supposons que ce soit vrai au rang N − 1. . . return a ; } Euh. . . 23/34 Les limites du formalisme mathématique Déclaratif vs. impératif Programmation Fonctionnelle Comment exprimer le concept de « racine carrée » ? Didier Verna E PITA ( y>0 sqrt(x) = y| 2 y =x Paradigme ( defun s q r t p ( s x ) ( and ( > s 0 ) (= (∗ s s ) x ) ) ) 1er ordre Pureté Lisp Lisp Évaluation Typage ( defun s q r t ( x ) ???) Haskell Résumé Haskell s q r t p : : F l o a t −> F l o a t −> Bool s q r t p s x = s > 0 && s∗s == x s q r t : : F l o a t −> F l o a t s q r t x = ??? Au final, il faut bien expliquer comment faire. . . Mais on repousse le problème : impératif ou fonctionnel pur ? 24/34 Évaluation stricte / lazy Quand calculer la valeur d’une expression ? Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Typage Résumé Stricte : Lisp Les arguments (expressions) sont évalués d’abord. Lazy (paresseuse) : Haskell Les expressions ne sont évaluées que quand le besoin s’en fait sentir, (idem pour les agrégats). La paresse : une vertu ? Intérêt : plus d’abstraction (ex. manipulation de listes infinies). Contrainte : pureté fonctionnelle requise (on ne peut pas s’appuyer sur l’ordre d’évaluation). 26/34 Application « La somme des carrés des entiers entre 1 et N » V, Rédemption Programmation Fonctionnelle Didier Verna E PITA « À partir de la liste des entiers, calculer la somme des carrés jusqu’à N » Paradigme 1er ordre Pureté Évaluation Typage Résumé Lisp Haskell ( defun i n t l i s t ( s ) ( cons s ( i n t l i s t (1+ s ) ) ) ) i n t l i s t : : I n t −> [ I n t ] i n t l i s t s = s : i n t l i s t ( s + 1) ( defun s s q l ( l n ) ( i f (= ( c a r l ) n ) (∗ n n ) (+ (∗ ( c a r l ) ( c a r l ) ) ( ssql ( cdr l ) n ) ) ) ) s s q l : : [ I n t ] −> s s q l ( x : xs ) n = i f then else ( defun ssq ( n ) ( ssql ( i n t l i s t 1) n ) ) ; ; Coffee t i m e . . . ; ; ^C^C^C^C ! ! ! ! ! ! 27/34 I n t −> I n t ( x == n ) n∗n x∗x + s s q l xs n ssq : : I n t −> I n t ssq n = s s q l ( i n t l i s t 1 ) n −− Not c o f f e e time , b u t . . . −− Stack o v e r f l o w ! ! Pseudo-paresse dans les langages impératifs On ne fait toujours que ce qu’on peut. . . Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Typage Résumé 28/34 Les structures de contrôle impératives. . . sont des formes embryonnaires d’évaluation lazy. if (1) { /* COMPUTED */ /* blah blah ... */ } else { /* NOT COMPUTED ! */ /* blah blah ... */ } Typage dans les langages fonctionnels Et dans les autres. . . Programmation Fonctionnelle Didier Verna E PITA Problème orthogonal à la programmation fonctionnelle Il n’y a qu’à regarder les politiques d’Ada, C, Ruby, PHP. . . Paradigme 1er ordre Typage statique : Haskell Pureté Évaluation Typage Résumé I I Les variables sont typées Vérification de type à la compilation Typage dynamique : Lisp I I I Les valeurs sont typées Vérification de type à l’exécution Common Lisp : typage explicite possible Terminologie floue : typage statique, dynamique, manifeste, fort, latent, doux etc. 30/34 Typage et polymorphisme Quelle forme de généricité ? Programmation Fonctionnelle Didier Verna E PITA Paradigme 1er ordre Pureté Évaluation Les contraintes du typage statique. . . Lisp Haskell ( defun i n v e r t ( l ) ( unless ( n u l l l ) ( append ( i n v e r t ( c d r l ) ) ( l i s t ( car l ) ) ) ) ) i n v e r t : : [ I n t ] −> [ I n t ] invert [] = [] i n v e r t ( x : xs ) = ( i n v e r t xs ) ++ [ x ] −− i n v e r t [ 1 . . 5 ] ; ; ( invert ; ; ( invert ’(1 2 3 4)) ’ ( foo 3.6 " blah " 2 ) ) Typage Résumé . . . sont compensées par le polymorphisme : Mais les listes restent homogènes. . . Haskell i n v e r t : : [ a ] −> [ a ] invert [] = [] i n v e r t ( x : xs ) = ( i n v e r t xs ) ++ [ x ] −− i n v e r t [ 1 . . 5 ] −− i n v e r t [ " a " , " b " , " c " ] 31/34 Pourquoi l’approche fonctionnelle est bénéfique Les 3 caractéristiques des (bons) langages Programmation Fonctionnelle Primitives Didier Verna E PITA Paradigme 1er ordre Procédures / Données Pureté Évaluation Typage Combinaisons Abstractions Résumé Moins de distinction entre procédures et données Plus de puissance dans la combinaison Plus de puissance dans l’abstraction 33/34 Entre Lisp et Haskell Deux approches fonctionnelles de la programmation Programmation Fonctionnelle Didier Verna E PITA Lisp Haskell Paradigme 1er ordre Pureté Évaluation Typage Résumé 34/34 ∗ ou pas. . . Fonct. impur∗ pur Évaluation stricte∗ lazy Typage dynamique∗ statique Autres ...∗ ...