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 !