Approches Fonctionnelles de la Programmation

publicité
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
...∗
...
Téléchargement