OCaml : un Langage Fonctionnel Alan Schmitt 26 septembre 2016 Informations Pratiques ▶ Équipe pédagogique ▶ ▶ Alan Schmitt (cours, 2 groupes de TP) Barbara Kordy (2 groupes de TP) ▶ 4 cours ▶ 9 TP notés ▶ pas d’examen ▶ http://www.irisa.fr/celtique/aschmitt/teaching/ [email protected] Pourquoi apprendre OCaml ? Objectifs de ce cours ▶ Comprendre des concepts fondamentaux de la programmation (types, récursion) ▶ Apprendre à exploiter les atouts de la programmation fonctionnelle Pourquoi ? ▶ Pourquoi s’intéresser à ce style de programmation ? ▶ Peut-on résoudre les mêmes problèmes qu’en C ou Java ? Pourquoi s’intéresser à la programmation fonctionnelle (1) Approche favorisant des programmes ▶ corrects; ▶ lisibles; ▶ réutilisables ou modifiables. Un langage de programmation est un outil pointu. Les langages fonctionnels sont le résultat de nombreuses années de recherche. Pourquoi s’intéresser à la programmation fonctionnelle (2) Langages fonctionnels proches des mathématiques. Ce sont des langages de haut niveau : ▶ permettant de s’abstraire de l’architecture des machine; ▶ donnant des programmes clairs et concis; ▶ favorisant un développement rapide; ▶ fournissant des outils pour une meilleure sûreté (types). Pourquoi s’intéresser à la programmation fonctionnelle (3) De nombreux langages intègrent des aspects fonctionnels: ▶ Java ▶ Objective C ▶ JavaScript ▶ Swift Exemple: gestion de la mémoire Allouer une liste en C: struct struct if (x1 x1->hd return list * x1 = malloc(sizeof(struct list)); list * x2 = malloc(sizeof(struct list)); == NULL || x2 == NULL) return NULL; = 1; x1->tl = x2; x2->hd = 2; x2->tl = NULL; x1; Allouer une liste en OCaml 1 :: 2 :: [] Peut-on tout programmer en OCaml? Règle 110�: Cellules Nouveau centre Exemple�: 00000001000 00000011000 00000111000 00001101000 00011111000 00110001000 111 0 110 1 101 1 100 0 011 1 010 1 001 1 000 0 Règle 110 en OCaml type cell = T | F let rule110 triple = match triple with | (T,T,T) | (T,F,F) | (F,F,F) -> F | _ -> T let map3 f l = let rec aux curl = match curl with | a :: b :: c :: tl -> f (a,b,c) :: aux (b :: c :: tl) | [a;b] -> [f(a,b,F)] | _ -> assert false in aux (F :: l) let init = [F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;F;T] let rec loop n cur = List.iter (fun F -> print_char ' ' | T -> print_char 'X') cur; print_newline (); if n < 20 then loop (n+1) (map3 rule110 cur) let _ = loop 0 init Influence Haskell F# Scala Swift Influence https://facebook.github.io/reason/ Grands succès https://ocaml.org/learn/success.html Apprendre OCaml http://ocaml.org Apprendre OCaml : des livres http://caml.inria.fr/pub/distrib/books/llc.pdf http://www.pps.jussieu.fr/Livres/ora/DA-OCAML/ http://programmer-avec-ocaml.lri.fr/ Apprendre OCaml : des leçons en ligne http://try.ocamlpro.com/ Apprendre OCaml : un MOOC MOOC commencé le 26 septembre, on peut encore s’inscrire https://www.fun-mooc.fr/courses/parisdiderot/ 56002S02/session02/about Introduction Un langage de la famille ML fonctionnel les fonctions sont des valeurs de première classe typé inférence de types et types polymorphes let composition f g = fun x -> f (g x) val composition : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun> filtrage avec déclaration de nouveaux types type cell = T | F let rule110 triple = match triple with | (T,T,T) | (T,F,F) | (F,F,F) -> F | _ -> T type cell = T | F val rule110 : cell * cell * cell -> cell = <fun> Un langage fonctionnel ▶ en maths: N→N n 7→ 2n + 3 ▶ en C (nom obligatoire, instruction return): int f (int x) { return 2*x + 3; } ▶ en OCaml fun x -> 2*x + 3 Avantages : ▶ tout est expression, assemblées comme des briques Lego ▶ raisonnement local (pas d’effet de bord) ▶ facile de créer des structures de données ▶ le typage capture des erreurs avant l’exécution Exemple let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1)) let res = fact 10 val fact : int -> int = <fun> val res : int = 3628800 Programmes Un programme est une suite de phrases let identifiant = expression comme dans let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1)) let res = fact 10 Exécuter un programme, c’est exécuter toutes ses phrases dans l’ordre (pas de main) Exécuter une phrase, c’est calculer la valeur de expression, et d’associer le nom identifiant à cette valeur Deux outils pour d’exécuter les programmes Interprète Méthode interactive pour écrire des programmes. OCaml exécute chaque phrase, et imprime le résultat de son évaluation sous la forme val identifiant : type = valeur. let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1)) let res = fact 10 val fact : int -> int = <fun> val res : int = 3628800 avantages interactif, permet d’évaluer des programmes partiels, donne les types et valeurs des phrases inconvénients lent, utilisable que pour le développement du programme, OCaml doit être installé Deux outils pour d’exécuter les programmes Compilateur Un outil transformant le programme en du code machine ocamlopt -o fact fact.ml ./fact avantages rapide, pas besoin d’avoir à installer OCaml inconvénients n’imprime rien, sauf si instruction spécifique let rec fact = fun n -> if n < 2 then 1 else n * (fact (n-1)) let res = fact 10 let () = Printf.printf "résultat : %d\n" res ocamlopt -o fact2 fact2.ml && ./fact2 résultat : 3628800 Les définitions En Caml une définition consiste à donner un nom à une valeur Syntaxe let identifiant = expression Exemple let x = 3 + 5 val x : int = 8 Définitions récursives Si expression mentionne nom (fonctions récursives), on utilise let rec nom = expression Portée des noms (* À ce niveau aucun nom n’est défini *) let x = (* x n’est toujours pas défini *) 1 + 2 (* x est maintenant défini et vaut 3 *) let x = (* x vaut toujours 3 *) x + 1 (* le x est l’ancien qui vaut 3 *) (* un nouveau nom x cache l’ancien, et vaut 4 *) let rec fact = (* fact est défini, et vaudra le résultat de l’évaluation du reste *) function n -> if n < 2 then 1 else n * (fact (n - 1)) (* le fact ci-dessus est le même que celui qu’on défini *) Portée des noms (* À ce niveau aucun nom n’est défini *) let x = (* x n’est toujours pas défini *) 1 + 2 (* x est maintenant défini et vaut 3 *) let x = (* x vaut toujours 3 *) x + 1 (* le x est l’ancien qui vaut 3 *) (* un nouveau nom x cache l’ancien, et vaut 4 *) let rec fact = (* fact est défini, et vaudra le résultat de l’évaluation du reste *) function n -> if n < 2 then 1 else n * (fact (n - 1)) (* le fact ci-dessus est le même que celui qu’on défini *) Le typage Le typage est une technique pour détecter automatiquement une classe d’erreurs de programmation: let x = 1 + 2 * 3 val x : int = 7 mais let x = 1 + "toto" Characters 12-18: let x = 1 + "toto";; ^^^^^^ Error: This expression has type string but an expression was expected of type int Limites du typage ▶ Correct et bien typé: let rec fact = fun n -> if n < 2 then 1 else fact(n - 1) * n val fact : int -> int = <fun> ▶ Incorrect et mal typé: let rec fact = fun n -> if n < 2 then 1 else fact(n - "toto") * n Characters 54-60: let rec fact = fun n -> if n < 2 then 1 else fact(n - "toto") * n ;; ^^^^^^ Error: This expression has type string but an expression was expected of type int ▶ Incorrect mais bien typé: let rec fact = fun n -> if n < 2 then 1 else fact(n + 1) * n val fact : int -> int = <fun> Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ les nombres Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ ▶ les nombres les fonctions Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ ▶ ▶ les nombres les fonctions … Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ ▶ ▶ ▶ les nombres les fonctions … les expressions réduites à un nom Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ ▶ ▶ les nombres les fonctions … ▶ les expressions réduites à un nom ▶ les appels de fonction Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ ▶ ▶ les nombres les fonctions … ▶ les expressions réduites à un nom ▶ les appels de fonction ▶ les expressions conditionnelles Les catégories d’expressions let rec fact = fun n -> if n < 2 then 1 else (fact (n - 1)) * n ▶ les expressions réduites à une valeur ▶ ▶ ▶ les nombres les fonctions … ▶ les expressions réduites à un nom ▶ les appels de fonction ▶ les expressions conditionnelles ▶ les expressions de filtrage Les valeurs élémentaires Les valeurs élémentaires : les nombres entiers ▶ valeurs dans [−230 , 230 − 1] ou [−262 , 262 − 1] ▶ type int ▶ opérateurs principaux : + ▶ - * / mod exemple : let _ = 5 / 2 - : int = 2 Les valeurs élémentaires : les nombres flottants ▶ valeurs : 41.5, 24e5 ▶ type : float ▶ opérateurs principaux : +. ▶ *. /. ** fonctions principales : sqrt ▶ -. log exp exemple : let _ = 5. /. 2. - : float = 2.5 sin cos asin acos tan atan Les valeurs élémentaires : les booléens ▶ les 2 valeurs : true false ▶ type : bool ▶ opérateurs principaux : not ▶ && || Attention: && et || sont évalués de gauche à droite Les valeurs élémentaires : les caractères ▶ valeurs : '3' 'a' ';' ▶ type : char ▶ opérations : voir cours sur la programmation impérative et http://caml.inria.fr/pub/docs/manual-ocaml/ libref/Char.html Les valeurs élémentaires : les chaînes ▶ valeurs : "Hello World!" ▶ type : string ▶ opérateurs principaux : ^ (concaténation), String.length ▶ http://caml.inria.fr/pub/docs/manual-ocaml/ libref/String.html Les valeurs élémentaires : l’unité ▶ unique valeur : () ▶ type : unit ▶ utilisé comme type de retour des fonctions qui ne renvoient rien let _ = print_endline - : string -> unit = <fun> ▶ on peut utiliser « () » à la place de « _ » quand on veut ignorer le résultat d’une fonction mais indiquer qu’elle ne retourne rien let () = print_endline "Bonjour" Bonjour Les valeurs élémentaires : les fonctions ▶ valeur de la forme fun variable -> expression fun x -> 3 * x + 1 - : int -> int = <fun> ▶ type argument -> résultat ▶ opérateur principal, l’application (dénoté par la juxtaposition) (fun x -> 3 * x + 1) 5 - : int = 16 Ces fonctions sont anonymes. Pour leur donner un nom, on utilise une définition: let mafonction = fun x -> 3 * x + 1 val mafonction : int -> int = <fun> Comment lire le type des fonctions ? Le parenthésage à droite est implicite dans l’expression du type des fonctions int -> int -> int -> int = int -> (int -> (int -> int)) Fonction qui prend un entier en argument, qui rend une fonction qui prend un entier en argument, qui rend une fonction de entier dans entier. Fonctions : ordre supérieur Une fonction est une valeur : elle peut être l’argument ou le résultat d’autres fonction. Une fonctionnelle est une fonction acceptant en argument et/ou rendant en résultat d’autres fonctions. Fonctionnelles : exercices Soit la fonction sin : float → float ▶ définir en Caml une fonction g g : x 7→ sinx x ▶ définir en Caml une fonctionnelle h h : f 7→ (x 7→ f(x) x ) Fonctionnelles : solutions let g = fun x -> (sin x)/.x val g : float -> float = <fun> let h = fun f -> (fun x -> (f x)/.x) val h : (float -> float) -> float -> float = <fun> let _ = h sin - : float -> float = <fun> (c’est la fonction sin(x)/x) let _ = fun x -> (h sin) x - : float -> float = <fun> (c’est évidemment la même fonction) Conversions int_of_float - : float -> int = <fun> float_of_int - : int -> float = <fun> int_of_char - : char -> int = <fun> … Valeurs composées : NUplets Valeurs: (e1, e2, e3, ...) Type: (t1 * t2 * t3 * ...) let _ = (1, 1.2, true) - : int * float * bool = (1, 1.2, true) let _ = fst((1,2+3),3) - : int * int = (1, 5) Valeurs : Bilan expression (e) valeurs atomiques 25 87.62 ’f’ true ”caml” () valeurs composées (e1, ..., en) valeurs fonctionnelles fun v -> e type (t) int float char bool string unit t1 * ... * tn t1 -> t2 Expressions Définitions Définir = donner un nom à une valeur let x = 5-3 val x : int = 2 let y = true val y : bool = true ocaml évalue la partie droite et l’associe au nom, que l’on peut ensuite utiliser: let _ = x*x - : int = 4 Définitions Attention: x désigne la valeur, pas l’expression let x = 5-3 val x : int = 2 let y = 2*x val y : int = 4 let x = 1 val x : int = 1 let _ = y - : int = 4 Définitions locales let nom = expr1 in expr2 Sémantique 1. on évalue expr1, qui donne val1 2. on évalue expr2 en ayant associé nom à val1 Exemple let _ = let x = (11 + 10) in x + x - : int = 42 Attention En OCaml, let est utilisé à la fois pour introduire une phrase et pour introduire une définition locale Noms Un nom est simplement un synonyme pour la valeur associée. L’évaluer revient à retourner cette valeur. Le type du nom est le type de la valeur. let x = 40 + 2 val x : int = 42 let _ = x - : int = 42 Expressions conditionnelles (1) if expr1 then expr2 else expr3 Sémantique 1. on évalue expr1, qui doit être de type booléen, en v1 2. selon v1, on évalue expr2 ou expr3, qui sont de même type Exemples let _ = (if 3=4 then 2+7 else 7-7*2) + 4 let _ = (if 3=4 then "foo" else 7-7*2) + 4 Characters 21-26: let _ = (if 3=4 then "foo" else 7-7*2) + 4;; ^^^^^ Error: This expression has type string but an expression was expected of type int - : int = -3 Expressions conditionnelles (2) On peut écrire if expr1 then expr2 qui est équivalent à if expr1 then expr2 else () =⇒ le type de expr2 doit être compatible avec unit let _ = if true then "foo" Characters 21-26: let _ = if true then "foo";; ^^^^^ Error: This expression has type string but an expression was expected of type unit Le filtrage Expressions de filtrage match expr with | motif1 -> expr1 | motif2 -> expr2 ... | motifn -> exprn Sémantique On évalue On évalue accepte la Toutes les expr et sa valeur v est confrontée aux motifs. ensuite l’expression associée au premier motif qui valeur v. branches doivent retourner une valeur du même type. Exemple let _ = let b = match b | true | false false in with -> "foo" -> "bar" - : string = "bar" Filtres de valeurs, filtre universel, combinaison ▶ fitrage de valeur (true, "foo", 3, …): le motif 3 est « rigide », il accepte un seul élément, l’élément 3. ▶ filtre universel (_): le motif _ est un motif qui accepte tous les éléments ▶ un filtre (m1,m2) accepte les paires (v1,v2) si m1 accepte v1 et m2 accepte v2. Par exemple, (true,_) accepte toutes les paires dont le premier élément est true : (true,true) (true,55) (true,(4,5.67)) ... Noms dans les motifs Un nom dans un motif est un filtre universel qui est associe le nom à la valeur acceptée dans l’expression associée let _ = match (1,2) with | (x,1) -> x | (x,2) -> x + 10 | (_,_) -> 0 - : int = 11 let sumpair = fun x -> match x with | (x1,x2) -> x1+x2 val sumpair : int * int -> int = <fun> C’est comme cela que l’on accède aux éléments d’un nuplet Noms dans les motifs Attention let x = 12 val x : int = 12 let egal_12 n = match n with | x -> true | _ -> false Characters 43-44: | _ -> false;; ^ Warning 11: this match case is unused. val egal_12 : 'a -> bool = <fun> let res = egal_12 42 val res : bool = true Un nom dans un motif n’est jamais remplacé par une valeur qui lui serait associée Définitions et motifs Les définitions globale ou locale sont en fait de la forme let motif = expression let motif = expression in expression On peut donc écrire : let (x,y) = (1+2, "foo" ^ "bar") val x : int = 3 val y : string = "foobar" let () = print_endline "hello" (* filtrage rigide sur () *) hello let _ = 12 - : int = 12 Fonctions et motifs Comme pour les définitions, les fonctions sont de la forme fun motif -> expression Exemple let sumpair = fun (x1,x2) -> x1+x2 val sumpair : int * int -> int = <fun> Syntaxe des fonctions let nom = fun x -> ... -> fun z -> expr peut s’écrire let nom x ... z = expr De la même manière, let sumpair = fun (x1,x2) -> x1+x2 peut s’écrire let sumpair (x1,x2) = x1+x2 Fonctions : combien d’arguments ? ▶ fonction à un argument let carre x = x*.x val carre : float -> float = <fun> ▶ fonction à plusieurs arguments (?) let distance (x,y) = sqrt(carre(x)+.carre(y)) val distance : float * float -> float = <fun> en fait un seul argument: un couple ▶ la version à deux arguments: let distance x y = sqrt(carre(x)+.carre(y)) val distance : float -> float -> float = <fun> Fonctions et argument () let foo () = print_endline "Hello" c’est une fonction val foo : unit -> unit = <fun> let _ = foo () let _ = foo () Hello Hello Sans (), le comportement est différent let foo = print_endline "Hello" Hello val foo : unit = () let _ = foo let _ = foo Exhaustivité Soit la définition : let trait_dir dir = match dir with | "nord" -> 0 | "sud" -> 1 | "est" -> 2 | "ouest" -> 3 quel est le comportement de Caml ? Exhaustivité Soit la définition : let trait_dir dir = match dir with | "nord" -> 0 | "sud" -> 1 | "est" -> 2 | "ouest" -> 3 quel est le comportement de Caml ? Characters 20-94: ....................match dir with | "nord" -> 0 | "sud" -> 1 | "est" -> 2 | "ouest" -> 3.. Warning 8: this pattern-matching is not exhaustive. Here is an example of a value that is not matched: "" val trait_dir : string -> int = <fun> La fonction et logique et : bool * bool -> bool x true true false false y true false true false let et (x,y) = match (x,y) with | (true,true) -> true | (true,false) -> false | (false,true) -> false | (false,false) -> false val et : bool * bool -> bool = <fun> et (x,y) true false false false Exercice de filtrage (1) Imaginer une définition de la fonction et plus concise Exercice de filtrage (1) Imaginer une définition de la fonction et plus concise let et (x,y) = match (x,y) with | (true,y) -> if y then true else false | (false,y) -> false val et : bool * bool -> bool = <fun> Exercice de filtrage (1) Imaginer une définition de la fonction et plus concise let et (x,y) = match (x,y) with | (true,y) -> if y then true else false | (false,y) -> false val et : bool * bool -> bool = <fun> let et (x,y) = match (x,y) with | (true,y) -> y | (false,y) -> false val et : bool * bool -> bool = <fun> Exercice de filtrage (1) Imaginer une définition de la fonction et plus concise let et (x,y) = match (x,y) with | (true,y) -> if y then true else false | (false,y) -> false val et : bool * bool -> bool = <fun> let et (x,y) = match (x,y) with | (true,y) -> y | (false,y) -> false val et : bool * bool -> bool = <fun> let et (x,y) = match x with | true -> y | false -> false val et : bool * bool -> bool = <fun> Exercice de filtrage (1) Imaginer une définition de la fonction et plus concise let et (x,y) = match (x,y) with | (true,y) -> if y then true else false | (false,y) -> false val et : bool * bool -> bool = <fun> let et (x,y) = match (x,y) with | (true,y) -> y | (false,y) -> false val et : bool * bool -> bool = <fun> let et (x,y) = match x with | true -> y | false -> false val et : bool * bool -> bool = <fun> let et (x,y) = if x then y else false val et : bool * bool -> bool = <fun> Restrictions sur les motifs ▶ une même variable ne peut apparaître qu’une seule fois par motif: le motif doit être linéaire let diag p = match p with | (x,x) -> true | _ -> false Characters 31-32: | (x,x) -> true ^ Error: Variable x is bound several times in this matching let diag p = match p with | (x,y) -> x=y val diag : 'a * 'a -> bool = <fun> ▶ les valeurs acceptées par les motifs ne sont pas forcément disjointes ▶ le premier motif qui accepte la valeur est celui utilisé Exercice de filtrage (2) Définir une fonction de type val table_ou : bool * bool -> (bool * bool) * bool = <fun> telle que table_ou (a,b) retourne une paire ((a,b),c) où c est la somme logique de a et b Exercice de filtrage (2) Définir une fonction de type val table_ou : bool * bool -> (bool * bool) * bool = <fun> telle que table_ou (a,b) retourne une paire ((a,b),c) où c est la somme logique de a et b let table_ou (a,b) = match (a,b) with | (false,false) -> ((false,false),false) | x -> (x,true) ou plus simplement let table_ou (a,b) = ((a,b), et (a,b)) val table_ou : bool * bool -> (bool * bool) * bool = <fun> et comme je n’aime pas les répétitions let table_ou (a,b) = let p = (a,b) in (p, et p) val table_ou : bool * bool -> (bool * bool) * bool = <fun> Filtrages particuliers ▶ Combinaison de motifs let trait_caract c = match c with | 'a' | 'e' | 'i' | 'o' | 'u' | 'y' -> "voyelle" | _ -> "pas voyelle" ▶ Filtrage d’intervalle de caractères let f c = match c with | 'a' .. 'z' -> "lettre" | _ -> "pas lettre" ▶ Nommage de la valeur filtrée, <motif> as <nom> let maj c = match c with | ('a'..'z' as l) -> char_of_int (int_of_char l - 32) | x -> x Exercice de filtrage (3) Supposons donnée une fonction: val f : int -> int * int * int = <fun> Écrire une fonction de type val g : int -> bool = <fun> telle que: ▶ g x = true si le triplet f x contient au moins un zéro, ▶ g x = false sinon. Exercice de filtrage (3) Supposons donnée une fonction: val f : int -> int * int * int = <fun> Écrire une fonction de type val g : int -> bool = <fun> telle que: ▶ g x = true si le triplet f x contient au moins un zéro, ▶ g x = false sinon. let g x = match f x with | (0,_,_) -> true | (_,0,_) -> true | (_,_,0) -> true | _ -> false Exercice de filtrage (3) Supposons donnée une fonction: val f : int -> int * int * int = <fun> Écrire une fonction de type val g : int -> bool = <fun> telle que: ▶ g x = true si le triplet f x contient au moins un zéro, ▶ g x = false sinon. let g x = match f x with | (0,_,_) -> true | (_,0,_) -> true | (_,_,0) -> true | _ -> false let g x = match f x with | (0,_,_) | (_,0,_) | (_,_,0) -> true | _ -> false Exercice de filtrage (4) Définir le prédicat doublon qui reçoit une paire en argument et rend vrai si les deux éléments de la paire sont identiques, faux sinon. Exercice de filtrage (4) Définir le prédicat doublon qui reçoit une paire en argument et rend vrai si les deux éléments de la paire sont identiques, faux sinon. let doublon (x,y) = x=y val doublon : 'a * 'a -> bool = <fun> Polymorphisme & Inférence de types Le polymorphisme certaines fonctions peuvent être appliquées à des arguments dont le type n’est pas tout le temps le même : fonctions génériques ou polymorphes let first (x,y) = x val first : 'a * 'b -> 'a = <fun> let _ = first (3, "foo") - : int = 3 let _ = first (true, first) - : bool = true Variables de type les parties génériques du type sont représentées par des variables de type notées 'a, 'b, … ▶ first: - : 'a * 'b -> 'a = <fun> ▶ doublon: - : 'a * 'a -> bool = <fun> ▶ un changement de structure let chg_struct ((x,y),z) = (y,(x,z)) val chg_struct : ('a * 'b) * 'c -> 'b * ('a * 'c) = <fun> Exercice : polymorphisme Définir la fonction identité val id : 'a -> 'a = <fun> qui renvoie son argument, et la fonction de composition val comp : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b = <fun> qui prend en argument une paire de fonction (f,g) et rend la fonction composée Exercice : polymorphisme Définir la fonction identité val id : 'a -> 'a = <fun> qui renvoie son argument, et la fonction de composition val comp : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b = <fun> qui prend en argument une paire de fonction (f,g) et rend la fonction composée let id x = x Exercice : polymorphisme Définir la fonction identité val id : 'a -> 'a = <fun> qui renvoie son argument, et la fonction de composition val comp : ('a -> 'b) * ('c -> 'a) -> 'c -> 'b = <fun> qui prend en argument une paire de fonction (f,g) et rend la fonction composée let id x = x let comp (f,g) = fun x -> f (g x) ou let comp (f,g) x = f (g x) Inférence de type OCaml devine les types, et utilise cette information pour compiler le programme. let add x y = x + y val add : int -> int -> int = <fun> n’est pas compilé comme let addf x y = x +. y val addf : float -> float -> float = <fun> C’est pour cela que l’on distingue + et +.. Récursivité Factorielle ▶ définition intuitive: fact n = 1 * 2 * … * n ▶ définition mathématique: ▶ fact(n) = 1 si n < 2 fact(n) = n ∗ fact(n − 1) si n ≥ 2 en OCaml let rec fact n = if n < 2 then 1 else n * (fact (n-1)) val fact : int -> int = <fun> let _ = fact 5 - : int = 120 Calcul de factorielle Pour calculer e1 * e2, on calcul d’abord e1, puis on calcule e2, puis on multiplie les résultats fact 5 5 * (fact 4) 5 * (4 * (fact 3)) 5 * (4 * (3 * (fact 2))) 5 * (4 * (3 * (2 * (fact 1)))) 5 * (4 * (3 * (2 * 1))) 5 * (4 * (3 * 2)) 5 * (4 * 6) 5 * 24 120 Lors de l’appel récursif à fact, on doit garder de l’information pour les calculs à faire après Fonctions récursives Une définition récursive valide comporte au moins 2 cas : ▶ un ou plusieurs cas de base (où figure un élément particulier) if n < 2 then 1 ▶ un ou plusieurs cas récursifs, utilisant la fonction en cours de définition else n * (fact (n-1)) Attention l’appel récursif doit « se rapprocher » du cas de base. Sinon: risque de boucle infinie let rec fact = fun n -> if n < 2 then 1 else n * (fact (n+1)) Anatomie d’une fonction récursive let rec funrec arg = (* code exécuté avant l’appel récursif *) Printf.printf "avant, arg = %d\n" arg; if arg <= 0 then 0 else (* appel récursif, il faut se souvenir de arg pour après *) let res = funrec (arg - 1) in (* retour de l’appel récursif, on peut utiliser res et arg *) Printf.printf "après, arg = %d, res = %d\n" arg res; (* valeur finale retournée *) (res + 10) let res = funrec 4 avant, arg = 4 avant, arg = 3 avant, arg = 2 avant, arg = 1 avant, arg = 0 après, arg = 1, res après, arg = 2, res après, arg = 3, res après, arg = 4, res val funrec : int -> val res : int = 40 = 0 = 10 = 20 = 30 int = <fun> Graphiquement Code Exécution Avant (arg) Avant (1) Récursion Avant (2) Après (arg) Avant (3) Avant (4) Après (4) Après (3) Après (2) Après (1) Récursion terminale Si on se souvient de trop de choses, on tombe en panne de mémoire let rec stupide n = if n <= 0 then 0 else 1 + (stupide (n-1)) let res = stupide 265000 Stack overflow during evaluation (looping recursion?). Récursion terminale Si on se souvient de trop de choses, on tombe en panne de mémoire let rec stupide n = if n <= 0 then 0 else 1 + (stupide (n-1)) let res = stupide 265000 Stack overflow during evaluation (looping recursion?). Si on retourne directement le résultat de l’appel récursif, alors OCaml optimise et ne se souviens plus des arguments let rec malin n acc = if n <= 0 then acc else let acc' = acc + 1 in malin (n-1) acc' let res = malin 265000 0 val malin : int -> int -> int = <fun> val res : int = 265000 Domaines de récursion ▶ cas classique: la récurrence sur les entiers ▶ ▶ ▶ listes ▶ ▶ ▶ cas de base pour 0 valeur en n+1 calculée avec la valeur pour n cas de base pour la liste vide valeur pour l’ajout d’un élément calculé avec la valeur pour le reste de la liste arbres, etc … Exemple U0 = 4 Un = 2Un−1 + 3n pour n > 0 let rec u x = if x = 0 then 4 else 2 * u (x-1) + 3 * x let res = u 10 val u : int -> int = <fun> val res : int = 10204 Version avec récursion terminale let urt x = let rec aux acc if n > x then else let acc' aux acc' in aux 4 1 let res = urt 10 n = acc = 2 * acc + 3 * n in (n+1) val urt : int -> int = <fun> val res : int = 10204 Récursivité croisée let rec nom1 = expr1 and nom2 = expr2 Les expressions expr1 et expr2 peuvent utiliser nom1 et nom2. let rec pair n = if n = 0 then true else impair (n-1) and impair n = if n = 0 then false else pair (n-1) val pair : int -> bool = <fun> val impair : int -> bool = <fun> Spécificités de Caml Opérateurs de comparaison = <> < > <= >= let _ = "foo" = "foo" - : bool = true let _ = "foo" <> "foo" - : bool = false let _ = "foo" < "bar" - : bool = false let _ = "foo" > "foo" - : bool = false Attention L’opérateur « == » existe mais il ne faut pas l’utiliser. Divers : les priorités en Caml (1) L’application est notée par la simple juxtaposition de la fonction et de son argument. expr expr Une suite d’applications est parenthésée par défaut à gauche. expr expr expr est équivalent à (expr expr) expr Divers : les priorités en Caml (2) priorités décroissantes 1. application: f x 2. opérateur arithmétiques: f x + 1 est (f x) + 1 3. construction de n-uplets: f x + 1,2 est ((f x) + 1),2 4. abstraction: fun x -> f x + 1,2 est fun x -> (((f x) + 1),2) Ne pas hésiter à mettre des parenthèses L’opérateur - est par défaut la soustraction et pas l’opposé: f -1 est « f moins un ». Pour appliquer une fonction f à -1 il faut écrire f (-1). Curryfication Comparer et typer les définitions : let f (x,y) = x + 2*y let g x y = x + 2*y let _ = f (4,5) - : int = 14 let _ = g 4 5 - : int = 14 Curryfication ▶ ▶ f doit toujours être appelée avec une paire d’entiers comme argument g peut être appelée avec un entier ou deux ▶ g 4 est la fonction fun y -> 4 + 2*y Application partielle let compdbl f x = f (f x) val compdbl : ('a -> 'a) -> 'a -> 'a = <fun> let square x = x*x val square : int -> int = <fun> compdbl square 5 - : int = 625 let puiss4 = compdbl square val puiss4 : int -> int = <fun> Un exemple Fonctionnelle d’itération Ecrire une fonctionnelle qui correspond à la sémantique de l’itération pour ▶ caractérisation à partir d’exemples de l’itération « pour » ▶ établissement des lois de récurrence ▶ écriture de la fonctionnelle repeter ▶ constat de l’efficacité de cette fonctionnelle Exemples d’itérations 1. calcul itératif de 2n 2. calcul itératif du terme de rang n de la suite ▶ ▶ u0 = 3 un = 3 ∗ un−1 + 5 3. calcul itératif du terme de rang n de la suite de fibonacci ▶ ▶ ▶ u0 = 0 u1 = 1 un = un−1 + un−2 La boucle classique puiss := 1 pour i:=1 à n faire puiss := puiss*2 init U := 3 pour i:=1 à n faire U := U*3+5 init f f La sémantique de toute itération (1) Similitudes: ▶ utilisation d’une variable v de cumul (U , puiss) ▶ initialisation de v le contenu de la variable subit à chaque pas une transformation v := f(v) ▶ ▶ ▶ f(x) = x*2 pour 2n f(x) = x*3+5 pour la suite Un La sémantique de toute itération (2) Schéma général x := x0 pour i := 1 à n faire x := f(x) La valeur à rendre est x. Elle dépend de x0, n et f. On cherche F telle que x = F(n, f, x0) La sémantique de toute itération (3) valeur de x en fonction de x0, n et f : n = 0 x = F(0, f, x0) = x0 n = 1 x = F(1, f, x0) = f(x0) n = 2 x = F(2, f, x0) = f(f(x0)) … cas général: x = F(n, f, x0) = fn (x0) La loi de récurrence F(n, f, x0) = fn (x0) = f(fn−1 (x0)) = f(F(n − 1, f, x0)) F est en fait la fonctionnelle repeter ▶ repeter n f x0 = x0 pour n = 0 ▶ repeter n f x0 = f (repeter (n-1) f x0) pour n > 0 La fonctionnelle repeter let rec repeter f x0 n = if n = 0 then x0 else f (repeter f x0 (n-1)) val repeter : ('a -> 'a) -> 'a -> int -> 'a = <fun> Utilisation de repeter Utiliser l’itérateur repeter pour calculer ▶ 2n ▶ le terme de rang n de la suite ▶ ▶ u0 = 3 un = 3 ∗ un−1 + 5 let puissde2 n = repeter (fun x -> x*2) 1 n val puissde2 : int -> int = <fun> let suite n = repeter (fun x -> 3*x+5) 3 n val suite : int -> int = <fun> On peut utiliser l’application partielle pour ne pas mentionner n let puissde2 = repeter (fun x -> x*2) 1 val puissde2 : int -> int = <fun> let suite = repeter (fun x -> 3*x+5) 3 val suite : int -> int = <fun> Fibonacci itératif Revenons à Fibonacci U := 0; V := 1; pour i:=2 à n faire Y := V; V := V+U; U := Y; Fibonacci itératif Revenons à Fibonacci U := 0; V := 1; pour i:=2 à n faire Y := V; V := V+U; U := Y; let fibo n = snd (repeter (fun (x,y) -> (x+y,x)) (1,0) n) val fibo : int -> int = <fun> Cumuler les termes d’une suite Définir sigma(n) = n ∑ Ui i=0 en organisant les calculs de façon à diminuer la complexité. acc:=0; pour i:=0 à n faire acc:=s+Ui Cumuler les termes d’une suite Définir sigma(n) = n ∑ Ui i=0 en organisant les calculs de façon à diminuer la complexité. acc:=0; pour i:=0 à n faire acc:=s+Ui let sigma n = snd(repeter (fun (t,acc) -> (3*t+5,acc+t)) (3,0) (n+1)) val sigma : int -> int = <fun>