cours

publicité
Introduction à la programmation
fonctionnelle
Pôle Informatique
24 janvier 2013
La programmation “normale” impérative
Un style de programmation est une façon d’exprimer un calcul.
Dans le style impératif :
I
programme : séquences d’instructions
I
instruction : ordre modifiant l’état de la mémoire
(variables/registres)
I
calcul : exécution des instructions, avec résultat lu dans une
variable
Ce modèle de calcul est directement inspiré des architectures
existantes.
L’alternative fonctionnelle
Dans le style fonctionnel
I
programme : une expression
I
expression : formule (ex : expression arithmétique)
I
calcul : évaluation de l’expression = déterminer la valeur
représentée par la formule
Exemples de programmes : 42, 1 + 1, cos(π / 2) * exp(1)
L’alternative fonctionnelle
Dans le style fonctionnel
I
programme : une expression
I
expression : formule (ex : expression arithmétique)
I
calcul : évaluation de l’expression = déterminer la valeur
représentée par la formule
Exemples de programmes : 42, 1 + 1, cos(π / 2) * exp(1)
⇒ pas de variables modifiables
⇒ pas de boucles for, while, etc . . .
L’alternative fonctionnelle
Dans le style fonctionnel
I
programme : une expression
I
expression : formule (ex : expression arithmétique)
I
calcul : évaluation de l’expression = déterminer la valeur
représentée par la formule
Exemples de programmes : 42, 1 + 1, cos(π / 2) * exp(1)
⇒ pas de variables modifiables
⇒ pas de boucles for, while, etc . . .
remplacées par
A language where functions are taken seriously.
(D. Rémy)
L’interpréteur OCaml
On programme à l’aide d’un interpréteur (comme dans python ou R),
qui :
I
lit une phrase en entrée (expression)
I
évalue l’expression
I
affiche le résultat
Sur un terminal :
[ gesundheit :~ 13:17]\ $ocaml
Objective Caml version 4.00.1
# 41 + 1;;
- : int = 42
L’interpréteur OCaml
On programme à l’aide d’un interpréteur (comme dans python ou R),
qui :
I
lit une phrase en entrée (expression)
I
évalue l’expression
I
affiche le résultat
Sur un terminal :
Invite
[ gesundheit :~ 13:17]\ $ocaml
Fin d’expression
Objective Caml version 4.00.1
#
- :
41 + 1 ;;
int
=
42
Type de l’expression
Résultat de l’évaluation
Formation programmation fonctionnelle
Cœur fonctionnel dans OCaml
Listes
Programmation par filtrage
Types somme
Le mot de la fin
Quelques types de données simples
Entier :
# 41 + 1;;
- : int = 42
Flottant :
# 3.14159265358979312;;
- : float = 3.14159265358979312
Booléens :
# true ;;
- : bool = true
Caractère :
# ’a ’;;
- : char = ’a ’
Quelques types de données simples
Chaîne de caractères :
#
#
-
" OCaml " ;;
: string = " OCaml "
" Molecular " ^ " " ^ " phylogeny " ;;
: string = " Molecular phylogeny "
Couples et n-uplets :
#
#
-
(0.5 ,1.3);;
: float * float = (0.5 , 1.3)
(1 , ’a ’ ,0.5);;
: int * char * float = (1 , ’a ’ , 0.5)
Note : dans tous les cas, l’interpréteur détermine seul le type de
l’expression
Nommer un résultat
Syntaxe : let identifiant = expression
# let x
val x :
# x ;;
- : int
# let y
val y :
# let x
val x :
# y ;;
- : int
I
= 1;;
int = 1
= 1
= x + 41;;
int = 42
= 0;;
int = 0
= 42
si l’on nomme successivement deux résultats d’évaluation avec
le même identifiant
I
I
seule la deuxième est accessible
la première “existe” toujours
Nommage local
Pour nommer des résultats intermédiaires :
# let y =
let x = 1 in
let z = 41 in
x + z ;;
val y : int = 42
# z ;;
Error : Unbound value z
I
la portée de z est limitée à la définition de y
I
z est donc inconnu (unbound) en dehors de cette définition
I
la définition locale de x masque toute définition antérieure liée à
x, jusqu’à la fin de la définition de y
if ... then ... else en fonctionnel
En style impératif, if ... then ... else est une structure de
contrôle, cette construction dirige le flot d’exécution.
En style fonctionnel, if ... then ... else est une expression :
# 21 * ( if 3 < 4 then 2 else 0);;
- : int = 42
⇒ la partie else est donc obligatoire.
Fonctions (at last)
(* Une expression repre ’ sentant une fonction *)
# fun x -> x + 1;;
- : int -> int = <fun >
Appliquer une fonction :
#
#
#
-
f (0);;
: int = 1
f (0);;
: int = 1
f 0;;
: int = 1
Fonctions (at last)
(* Une expression repre ’ sentant une fonction *)
# fun x -> x + 1;;
- : int -> int = <fun >
(* On peut la lier ‘a un identifiant *)
# let f = fun x -> x + 1;;
val f : int -> int = <fun >
Appliquer une fonction :
#
#
#
-
f (0);;
: int = 1
f (0);;
: int = 1
f 0;;
: int = 1
Fonctions (at last)
(* Une expression repre ’ sentant une fonction *)
# fun x -> x + 1;;
- : int -> int = <fun >
(* On peut la lier ‘a un identifiant *)
# let f = fun x -> x + 1;;
val f : int -> int = <fun >
(* Une syntaxe plus compacte *)
# let f x = x + 1;;
val f : int -> int = <fun >
Appliquer une fonction :
#
#
#
-
f (0);;
: int = 1
f (0);;
: int = 1
f 0;;
: int = 1
Fonctions à plusieurs arguments
Syntaxe :
# fun x y -> x
- : int -> int
# let plus x y
val plus : int
# plus 1 41;;
- : int = 42
+ y ;;
-> int = <fun >
= x + y ;;
-> int -> int = <fun >
Fonctions à plusieurs arguments
Syntaxe :
# fun x y -> x
- : int -> int
# let plus x y
val plus : int
# plus 1 41;;
- : int = 42
+ y ;;
-> int = <fun >
= x + y ;;
-> int -> int = <fun >
“Fausse” fonction à plusieurs arguments :
# let plus ’ (x , y ) = x + y ;;
val plus ’ : int * int -> int = <fun >
# plus ’(1 , 41);;
- : int = 42
Fonctions à plusieurs arguments
Syntaxe :
# fun x y -> x
- : int -> int
# let plus x y
val plus : int
# plus 1 41;;
- : int = 42
+ y ;;
-> int = <fun >
= x + y ;;
-> int -> int = <fun >
“Fausse” fonction à plusieurs arguments :
# let plus ’ (x , y ) = x + y ;;
val plus ’ : int * int -> int = <fun >
# plus ’(1 , 41);;
- : int = 42
Les “vraies” sont plus flexibles (application partielle) :
# let succ = plus 1;;
val succ : int -> int = <fun >
# succ 41;;
- : int = 42
L’application partielle permet de spécialiser des fonctions.
Quelques exemples
# let abs x = if x > 0 then x else - x ;;
val abs : int -> int = <fun >
# abs ( - 2);;
- : int = 2
Quelques exemples
# let abs x = if x > 0 then x else - x ;;
val abs : int -> int = <fun >
# abs ( - 2);;
- : int = 2
(* Fonction identite ’ ( ’ a est une variable de type ) *)
# let id x = x ;;
val id : ’a -> ’a = <fun >
# ( abs ( id 1) , id ’1 ’);;
- : int * char = (1 , ’1 ’)
Quelques exemples
# let abs x = if x > 0 then x else - x ;;
val abs : int -> int = <fun >
# abs ( - 2);;
- : int = 2
(* Fonction identite ’ ( ’ a est une variable de type ) *)
# let id x = x ;;
val id : ’a -> ’a = <fun >
# ( abs ( id 1) , id ’1 ’);;
- : int * char = (1 , ’1 ’)
(* Projection *)
# let fst (x , _ ) = x ;;
val fst : ’a * ’b -> ’a = <fun >
Quelques exemples
# let abs x = if x > 0 then x else - x ;;
val abs : int -> int = <fun >
# abs ( - 2);;
- : int = 2
(* Fonction identite ’ ( ’ a est une variable de type ) *)
# let id x = x ;;
val id : ’a -> ’a = <fun >
# ( abs ( id 1) , id ’1 ’);;
- : int * char = (1 , ’1 ’)
(* Projection *)
# let fst (x , _ ) = x ;;
val fst : ’a * ’b -> ’a = <fun >
(* Composition *)
# let compose f g = fun x -> g ( f x );;
val compose : ( ’ a -> ’b ) -> ( ’ b -> ’c ) -> ’a -> ’c = <fun >
Itération sans boucle
Les boucles n’étant pas disponibles, on utilise des fonctions
récursives
Itération sans boucle
Les boucles n’étant pas disponibles, on utilise des fonctions
récursives
Exemple : fonction factorielle
# let rec fact n =
if n <= 0 then 1
else n * fact ( n - 1)
;;
val fact : int -> int = <fun >
# fact 4;;
- : int = 24
I
le mot-clef rec indique que fact est récursive
I
la récursion est utilisée pour itérer (à la place d’une boucle)
Un exemple : itérer une fonction n fois
Soit une fonction f , comment calculer f (n) (x) = f (f (...(f (x))...)) ?
Un exemple : itérer une fonction n fois
Soit une fonction f , comment calculer f (n) (x) = f (f (...(f (x))...)) ?
Définition récursive :
f 0 (x) = x
f (n+1) (x) = f (n) (f (x))
Un exemple : itérer une fonction n fois
Soit une fonction f , comment calculer f (n) (x) = f (f (...(f (x))...)) ?
Définition récursive :
f 0 (x) = x
f (n+1) (x) = f (n) (f (x))
# let rec power f n x =
if n = 0 then
x
else
power f ( n - 1) ( f x )
;;
val power : ( ’ a -> ’a ) -> int -> ’a -> ’a = <fun >
# power ( plus 1) 5 0;;
- : int = 5
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
# sin ;;
- : float -> float = <fun >
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
# sin ;;
- : float -> float = <fun >
# let sin ’ = derivative 0.001 sin ;;
val sin ’ : float -> float = <fun >
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
# sin ;;
- : float -> float = <fun >
# let sin ’ = derivative 0.001 sin ;;
val sin ’ : float -> float = <fun >
# let pi = 4. *. atan 1.;;
val pi : float = 3.14159265358979312
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
# sin ;;
- : float -> float = <fun >
# let sin ’ = derivative 0.001 sin ;;
val sin ’ : float -> float = <fun >
# let pi = 4. *. atan 1.;;
val pi : float = 3.14159265358979312
# ( sin ’ pi , cos pi );;
- : float * float = ( -0.999999833333231503 , -1.)
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
# sin ;;
- : float -> float = <fun >
# let sin ’ = derivative 0.001 sin ;;
val sin ’ : float -> float = <fun >
# let pi = 4. *. atan 1.;;
val pi : float = 3.14159265358979312
# ( sin ’ pi , cos pi );;
- : float * float = ( -0.999999833333231503 , -1.)
# let sin ’ ’ ’ = power ( derivative 0.001) 3 sin ;;
val sin ’ ’ ’ : float -> float = <fun >
Un autre exemple : fonctions dérivées
# let derivative eps f = fun x ->
( f ( x +. eps ) -. f x ) /. eps
;;
val derivative : float -> ( float -> float ) -> float -> float = <fun
# sin ;;
- : float -> float = <fun >
# let sin ’ = derivative 0.001 sin ;;
val sin ’ : float -> float = <fun >
# let pi = 4. *. atan 1.;;
val pi : float = 3.14159265358979312
# ( sin ’ pi , cos pi );;
- : float * float = ( -0.999999833333231503 , -1.)
# let sin ’ ’ ’ = power ( derivative 0.001) 3 sin ;;
val sin ’ ’ ’ : float -> float = <fun >
# sin ’ ’ ’ pi ;;
- : float = 0.999998750472741449
Formation programmation fonctionnelle
Cœur fonctionnel dans OCaml
Listes
Programmation par filtrage
Types somme
Le mot de la fin
Définition et notation
Rappel : une liste est une collection ordonnée de valeurs.
Définition récursive : une liste est
I
soit vide et notée []
I
soit constituée d’un premier élément h et d’une liste contenant le
reste des éléments t, le tout étant noté h :: t
# [];;
- : ’a list = []
Définition et notation
Rappel : une liste est une collection ordonnée de valeurs.
Définition récursive : une liste est
I
soit vide et notée []
I
soit constituée d’un premier élément h et d’une liste contenant le
reste des éléments t, le tout étant noté h :: t
#
#
-
[];;
: ’a list = []
1 :: [];;
: int list = [1]
Définition et notation
Rappel : une liste est une collection ordonnée de valeurs.
Définition récursive : une liste est
I
soit vide et notée []
I
soit constituée d’un premier élément h et d’une liste contenant le
reste des éléments t, le tout étant noté h :: t
#
#
#
-
[];;
: ’a list = []
1 :: [];;
: int list = [1]
1 :: 2 :: [];;
: int list = [1; 2]
Définition et notation
Rappel : une liste est une collection ordonnée de valeurs.
Définition récursive : une liste est
I
soit vide et notée []
I
soit constituée d’un premier élément h et d’une liste contenant le
reste des éléments t, le tout étant noté h :: t
#
#
#
#
-
[];;
: ’a list = []
1 :: [];;
: int list = [1]
1 :: 2 :: [];;
: int list = [1; 2]
[ 1 ; 2 ];;
: int list = [1; 2]
Définition et notation
Rappel : une liste est une collection ordonnée de valeurs.
Définition récursive : une liste est
I
soit vide et notée []
I
soit constituée d’un premier élément h et d’une liste contenant le
reste des éléments t, le tout étant noté h :: t
#
#
#
#
#
[];;
: ’a list = []
1 :: [];;
: int list = [1]
1 :: 2 :: [];;
: int list = [1; 2]
[ 1 ; 2 ];;
: int list = [1; 2]
1 :: " 2 " :: [];;
Définition et notation
Rappel : une liste est une collection ordonnée de valeurs de même
type.
Définition récursive : une liste est
I
soit vide et notée []
I
soit constituée d’un premier élément h et d’une liste contenant le
reste des éléments t, le tout étant noté h :: t
#
#
#
#
#
[];;
: ’a list = []
1 :: [];;
: int list = [1]
1 :: 2 :: [];;
: int list = [1; 2]
[ 1 ; 2 ];;
: int list = [1; 2]
1 :: " 2 " :: [];;
^^^
Error : This expression has type string but an expression
was expected of type int
Quelques opérations sur les listes
(* Calcul de la longueur *)
# List . length ;;
- : ’a list -> int = <fun >
# List . length [ 1 ; 2 ; 3 ];;
- : int = 3
Quelques opérations sur les listes
(* Calcul de la longueur *)
# List . length ;;
- : ’a list -> int = <fun >
# List . length [ 1 ; 2 ; 3 ];;
- : int = 3
(* Renversement *)
# List . rev ;;
- : ’a list -> ’a list = <fun >
# List . rev [ 1 ; 2 ; 3 ];;
- : int list = [3; 2; 1]
Quelques opérations sur les listes
(* Calcul de la longueur *)
# List . length ;;
- : ’a list -> int = <fun >
# List . length [ 1 ; 2 ; 3 ];;
- : int = 3
(* Renversement *)
# List . rev ;;
- : ’a list -> ’a list = <fun >
# List . rev [ 1 ; 2 ; 3 ];;
- : int list = [3; 2; 1]
(* Concate ’ nation *)
# ( @ );;
- : ’a list -> ’a list -> ’a list = <fun >
# [ 1 ; 2 ; 3 ] @ [ 4 ; 5 ; 6];;
- : int list = [1; 2; 3; 4; 5; 6]
Itérer sur une collection sans boucle
Filtrer les éléments d’une liste
#
#
-
List . filter ( fun x -> x < 3) [ 0 ; 3 ; 2 ; 5 ; 1 ];;
: int list = [0; 2; 1]
List . filter ;;
: ( ’ a -> bool ) -> ’a list -> ’a list = <fun >
Itérer sur une collection sans boucle
Filtrer les éléments d’une liste
#
#
-
List . filter ( fun x -> x < 3) [ 0 ; 3 ; 2 ; 5 ; 1 ];;
: int list = [0; 2; 1]
List . filter ;;
: ( ’ a -> bool ) -> ’a list -> ’a list = <fun >
Partitionner les éléments d’une liste
#
#
-
List . partition ( fun x -> x < 3) [ 0 ; 3 ; 2 ; 5 ; 1 ];;
: int list * int list = ([0; 2; 1] , [3; 5])
List . partition ;;
: ( ’ a -> bool ) -> ’a list -> ’a list * ’a list = <fun >
Itérer sur une collection sans boucle
Filtrer les éléments d’une liste
#
#
-
List . filter ( fun x -> x < 3) [ 0 ; 3 ; 2 ; 5 ; 1 ];;
: int list = [0; 2; 1]
List . filter ;;
: ( ’ a -> bool ) -> ’a list -> ’a list = <fun >
Partitionner les éléments d’une liste
#
#
-
List . partition ( fun x -> x < 3) [ 0 ; 3 ; 2 ; 5 ; 1 ];;
: int list * int list = ([0; 2; 1] , [3; 5])
List . partition ;;
: ( ’ a -> bool ) -> ’a list -> ’a list * ’a list = <fun >
Transformer chaque élément d’une liste
#
#
-
List . map ( fun x -> x * x ) [ 0 ; 1 ; 2 ; 3 ];;
: int list = [0; 1; 4; 9]
List . map ;;
: ( ’ a -> ’b ) -> ’a list -> ’b list = <fun >
Itérer sur une collection sans boucle (bis)
Aggréger les éléments d’une liste
I
soit une liste [ a1 ; a2 ; ... ; an ]
I
une valeur init
I
une fonction f dite d’aggrégation
I
calculer f (...(f (f init a1) a2)...)
Itérer sur une collection sans boucle (bis)
Aggréger les éléments d’une liste
I
soit une liste [ a1 ; a2 ; ... ; an ]
I
une valeur init
I
une fonction f dite d’aggrégation
I
calculer f (...(f (f init a1) a2)...)
#
#
-
List . fold_left ;;
: ( ’ a -> ’b -> ’a ) -> ’a -> ’b list -> ’a = <fun >
List . fold_left ( + ) 0 [ 1 ; 2 ; 3 ];;
: int = 6
Formation programmation fonctionnelle
Cœur fonctionnel dans OCaml
Listes
Programmation par filtrage
Types somme
Le mot de la fin
Analyse par cas
Le mot-clef function permet de définir une fonction par cas :
(* fonction not *)
# let f = function
| true -> false
| false -> true
;;
val f : bool -> bool = <fun >
Cette analyse par cas est appelée filtrage.
Analyse par cas
Le mot-clef function permet de définir une fonction par cas :
(* fonction not *)
# let f = function
| true -> false
| false -> true
;;
val f : bool -> bool = <fun >
(* Suite de Fibonnacci *)
# let rec fibo = function
| 0 -> 0
| 1 -> 1
| n -> fibo ( n - 1) + fibo ( n - 2)
;;
val fibo : int -> int = <fun >
Cette analyse par cas est appelée filtrage.
Filtrage sur listes
Le filtrage est applicable sur les valeurs de (quasiment) tout type, en
particulier sur les listes :
# let liste_vide = function
| [] -> true
| h :: t -> false
;;
val liste_vide : ’a list -> bool = <fun >
Filtrage sur listes
Le filtrage est applicable sur les valeurs de (quasiment) tout type, en
particulier sur les listes :
# let liste_vide = function
| [] -> true
| h :: t -> false
;;
val liste_vide : ’a list -> bool = <fun >
(* Calcul de la longueur d ’ une liste *)
# let rec longueur_liste = function
| [] -> 0
| _ :: t -> 1 + longueur_liste t
;;
val longueur_liste : ’a list -> int = <fun >
Cas manquants
Le compilateur peut trouver les éventuels cas manquants à la
compilation !
# let f = function
| 0 -> 0
| 1 -> 1
;;
Warning 8: this pattern - matching is not exhaustive .
Here is an example of a value that is not matched :
2
val f : int -> int = <fun >
Cas manquants
Le compilateur peut trouver les éventuels cas manquants à la
compilation !
# let f = function
| 0 -> 0
| 1 -> 1
;;
Warning 8: this pattern - matching is not exhaustive .
Here is an example of a value that is not matched :
2
val f : int -> int = <fun >
# let f = function
| [] -> 0
| (_ , ( ’a ’ , _ )) :: _ -> 42
| ( " 42 " , (_ , f )) :: _ -> int_of_float f
;;
Warning 8: this pattern - matching is not exhaustive .
Here is an example of a value that is not matched :
( " " , ( ’b ’ , _ )):: _
val f : ( string * ( char * float )) list -> int = <fun >
Application : quicksort
Quicksort en C (Rosetta code)
void quick_sort ( int *a , int n ) {
if ( n < 2)
return ;
int p = a [ n / 2];
int * l = a ;
int * r = a + n - 1;
while ( l <= r ) {
if (* l < p ) {
l ++;
continue ;
}
if (* r > p ) {
r - -;
continue ;
}
int t = * l ;
* l ++ = * r ;
*r - - = t ;
}
quick_sort (a , r - a + 1);
quick_sort (l , a + n - l );
}
Application : quicksort
Quicksort avec OCaml
let rec quicksort = function
| [] -> []
| h :: t ->
let l , r = List . partition ( fun x -> x < h ) t in
( quicksort l ) @ h :: ( quicksort r )
;;
Formation programmation fonctionnelle
Cœur fonctionnel dans OCaml
Listes
Programmation par filtrage
Types somme
Le mot de la fin
Système de types
Définition (informelle) : règles permettant au compilateur de détecter
des expressions mal définies.
I
ex : [ ’a’ ] + 1
I
ex de règle :
Si f : α → β et x : α alors f x est défini et est de type β.
Système de types
Définition (informelle) : règles permettant au compilateur de détecter
des expressions mal définies.
I
ex : [ ’a’ ] + 1
I
ex de règle :
Si f : α → β et x : α alors f x est défini et est de type β.
Le langage OCaml :
I
est typé statiquement (tous les types sont connus et vérifiés lors
de la compilation)
I
permet une inférence automatique des types
I
offre de nombreuses constructions pour définir des types :
I
fonctions, n-uplets, records, types somme, types privés,
valeurs paresseuses, types fantômes, variants
polymorphes, classes, objets, modules de première classe,
GADT
Système de types
Définition (informelle) : règles permettant au compilateur de détecter
des expressions mal définies.
I
ex : [ ’a’ ] + 1
I
ex de règle :
Si f : α → β et x : α alors f x est défini et est de type β.
Le langage
OCaml :
En pratique
I
utilise
(détourne(tous
?) le les
système
types
pouret vérifiés lors
estOn
typé
statiquement
types de
sont
connus
détecter
le
maximum
d’erreurs
lors
de
la
compilation.
de la compilation)
I
permet une inférence automatique des types
I
offre de nombreuses constructions pour définir des types :
I
fonctions, n-uplets, records, types somme, types privés,
valeurs paresseuses, types fantômes, variants
polymorphes, classes, objets, modules de première classe,
GADT
Types somme
Types de données où on a plusieurs cas possibles (énumérations) :
# type nucleotide = A | C | G | T ;;
type nucleotide = A | C | G | T
Les symboles A, C, G et T sont appelés constructeurs (sous-entendu
du type nucleotide).
Types somme
Types de données où on a plusieurs cas possibles (énumérations) :
# type nucleotide = A | C | G | T ;;
type nucleotide = A | C | G | T
# A ;;
- : nucleotide = A
Les symboles A, C, G et T sont appelés constructeurs (sous-entendu
du type nucleotide).
Types somme
Types de données où on a plusieurs cas possibles (énumérations) :
# type nucleotide = A | C | G | T ;;
type nucleotide = A | C | G | T
# A ;;
- : nucleotide = A
# (A , C );;
- : nucleotide * nucleotide = (A , C )
Les symboles A, C, G et T sont appelés constructeurs (sous-entendu
du type nucleotide).
Types somme
Types de données où on a plusieurs cas possibles (énumérations) :
# type nucleotide = A | C | G | T ;;
type nucleotide = A | C | G | T
# A ;;
- : nucleotide = A
# (A , C );;
- : nucleotide * nucleotide = (A , C )
# [ A ; T ; A ; T ];;
- : nucleotide list = [ A ; T ; A ; T ]
Les symboles A, C, G et T sont appelés constructeurs (sous-entendu
du type nucleotide).
Types somme
Types de données où on a plusieurs cas possibles (énumérations) :
# type nucleotide = A | C | G | T ;;
type nucleotide = A | C | G | T
# A ;;
- : nucleotide = A
# (A , C );;
- : nucleotide * nucleotide = (A , C )
# [ A ; T ; A ; T ];;
- : nucleotide list = [ A ; T ; A ; T ]
# type sequence = nucleotide list ;;
type sequence = nucleotide list
Les symboles A, C, G et T sont appelés constructeurs (sous-entendu
du type nucleotide).
Types somme et filtrage
Les types somme sont particulièrement utiles avec le filtrage
# let complement = function
| A -> T
| T -> A
| C -> G
| G -> C
;;
val complement : nucleotide -> nucleotide = <fun >
Types somme et filtrage
Les types somme sont particulièrement utiles avec le filtrage
# let complement = function
| A -> T
| T -> A
| C -> G
| G -> C
;;
val complement : nucleotide -> nucleotide = <fun >
En comparaison, l’utilisation de caractères ne fournit pas la même
garantie
# let complement = function
| ’A ’ -> ’T ’
| ’T ’ -> ’A ’
| ’C ’ -> ’G ’
| ’G ’ -> ’C ’
;;
Warning 8: this pattern - matching is not exhaustive .
Here is an example of a value that is not matched :
’a ’
val complement : char -> char = <fun >
Constructeurs non constants
Les constructeurs peuvent avoir un paramètre :
type couleur = | Trefle | Carreau | Coeur | Pique
Constructeurs non constants
Les constructeurs peuvent avoir un paramètre :
type couleur = | Trefle | Carreau | Coeur | Pique
type carte =
| As of couleur
| Roi of couleur
| Dame of couleur
| Valet of couleur
| Petite_carte of int * couleur
Constructeurs non constants
Les constructeurs peuvent avoir un paramètre :
type couleur = | Trefle | Carreau | Coeur | Pique
type carte =
| As of couleur
| Roi of couleur
| Dame of couleur
| Valet of couleur
| Petite_carte of int * couleur
let main = [ As Coeur ; Petite_carte (7 , Pique ) ; Valet Trefle ]
Constructeurs non constants
Les constructeurs peuvent avoir un paramètre :
type couleur = | Trefle | Carreau | Coeur | Pique
type carte =
| As of couleur
| Roi of couleur
| Dame of couleur
| Valet of couleur
| Petite_carte of int * couleur
let main = [ As Coeur ; Petite_carte (7 , Pique ) ; Valet Trefle ]
let valeur_d ’ une_carte couleur_d ’ atout = function
| As _ -> 11
| Roi _ -> 4
| Dame _ -> 3
| Valet c -> if c = couleur_d ’ atout then 20 else 2
| Petite_carte (10 , _ ) -> 10
| Petite_carte (9 , c ) -> if c = couleur_d ’ atout then 14 else 0
| Petite_carte _ -> 0
Type somme récursifs
Outil indispensable pour les structures de données de type liste ou
arbre :
(* De ’ finition du type re ’ cursif *)
# type ’a list = Empty | Cons of ’a * ’a list ;;
type ’a list = Empty | Cons of ’a * ’a list
(* La liste vide *)
# Empty ;;
- : ’a list = Empty
(* La liste 1 :: 2 :: [] *)
# Cons (1 , Cons (2 , Empty ));;
- : int list = Cons (1 , Cons (2 , Empty ))
Type somme récursifs
Outil indispensable pour les structures de données de type liste ou
arbre :
(* De ’ finition du type re ’ cursif *)
# type ’a list = Empty | Cons of ’a * ’a list ;;
type ’a list = Empty | Cons of ’a * ’a list
(* La liste vide *)
# Empty ;;
- : ’a list = Empty
(* La liste 1 :: 2 :: [] *)
# Cons (1 , Cons (2 , Empty ));;
- : int list = Cons (1 , Cons (2 , Empty ))
On les manipule à l’aide de fonctions récursives :
# let rec length = function
| Empty -> 0
| Cons (_ , t ) -> 1 + length t
;;
val length : ’a list -> int = <fun >
Formation programmation fonctionnelle
Cœur fonctionnel dans OCaml
Listes
Programmation par filtrage
Types somme
Le mot de la fin
Gestion de la mémoire
I
complètement automatique, effectuée par un ramasse-miette
I
I
I
pas de pointeurs
I
I
pour tout type de structure de données, aussi complexe
soit-elle (arbre de matrices, tables de hachages, graphes,
etc . . .)
pas besoin de penser aux malloc/new/free/delete
pas d’erreur de segmentation possible
les valeurs sont nécessairement initialisées à leur définition
I
pas de NullPointerException (Java)
Typage
I
typage statique
I
I
I
inférence de type
I
I
I
I
pas besoin d’attendre l’exécution pour détecter les
expressions mal formées
le compilateur montre l’emplacement exact de l’erreur et
explique ce qu’il attend
aucune annotation de type n’est nécessaire comme dans
un langage de script
mais tous les types sont vérifiés à la compilation (le beurre
et l’argent du beurre)
rien n’empêche d’ajouter des annotations de type pour
améliorer la lisibilité
grâce au filtrage, le compilateur vérifie la validité du code (sur
certains aspects) et propose des contre-exemples de cas
non-couverts
Typage (avancé)
Avec un codage approprié, le compilateur peut vérifier statiquement
des aspects très divers du fonctionnement du programme, et par
exemple assurer :
I
une bonne utilisation des formats de fichiers (e.g. détecter à la
compilation qu’on essaie de donner du GFF au programme
bowtie)
I
que les opérations sur une base de données sont compatibles
avec son schéma
I
que le programme accède à des ressources en respectant leurs
permissions
I
qu’un parser de fichier ne puisse pas produire des contenus mal
typés
I
inversement, qu’une programme produisant des pages HTML
produira toujours du HTML valide
Les fonctions prises au sérieux
Les boucles en style impératif donnent d’innombrables occasions de
faire des erreurs. En style fonctionnel, elles sont remplacées par :
I
des fonctions récursives, où les “variables” contrôlant l’itération
sont explicitement identifiées (moins de risque d’oublier de les
mettre à jour)
I
des fonctions d’ordre supérieur (List.map, List.filter ...),
qui “cachent” la boucle (qui a été écrite soigneusement une fois
pour toute)
Les fonctions prises au sérieux
Les boucles en style impératif donnent d’innombrables occasions de
faire des erreurs. En style fonctionnel, elles sont remplacées par :
I
des fonctions récursives, où les “variables” contrôlant l’itération
sont explicitement identifiées (moins de risque d’oublier de les
mettre à jour)
I
des fonctions d’ordre supérieur (List.map, List.filter ...),
qui “cachent” la boucle (qui a été écrite soigneusement une fois
pour toute)
Dans le même ordre d’idée, on peut utiliser des fonctions d’ordre
supérieur pour accéder à une ressource (fichier, connexion base de
données) sans oublier de la rendre :
f = open ( ’ delme . soon ’)
x = f . read (1)
...
f . close ()
with_file " delme . soon " ( fun file ->
let x = input_char file in
...
)
Ne soyons pas sectaires
Il existe une grande diversité dans les langages fonctionnels :
I
les ancêtres
I
I
Lisp, Common Lisp, Scheme
la famille ML
I
SML, OCaml, F#
I
Haskell
I
Scala
That’s all folks !
Téléchargement