Programme 5 : Tracé de courbe Programmation fonctionnelle Notions introduites : Notes de cours I n-uplets, tableaux I ordre d’évaluation fonctions de première classe, ordre supérieur fonctions anonymes filtrage I I I Cours 2 4 20 15 2 2 30 10 10 15 18 Septembre 2013 Sylvain Conchon [email protected] 1/19 12/19 Les n-uplets Les n-uplets Expressions de type produit # # # # # I I I 3/19 2 Attention à ne pas confondre les types des expressions suivantes : (1, ’a’) ;; : int * char = (1, ’a’) (1 + 2, ’a’, true && false) ;; : int * char * bool = (3, ’a’, false) ("hello", (1, 3.4)) ;; : string * (int * float) = ("hello", (1, 3.4)) fst (1, ’a’) ;; : int = 1 snd ((1, ’a’), (2+3, true, "a")) ;; : int * bool * string = (5, true, "a") # # # - (1, 2, 3) ;; : int * int * int = (1, 2, 3) ((1, 2), 3) ;; : (int * int) * int = ((1, 2), 3) (1, (2, 3)) ;; : int * (int * int) = (1, (2, 3)) I int * int * int désigne un triplet d’entiers I (int * int) * int est une paire dont la première composante est une paire d’entiers et la deuxième un entier le type produit * représente les n-uplets les n-uplets peuvent être arbitrairement imbriqués fst et snd permettent d’accéder respectivement à la première et à la deuxième composante d’une valeur de type τ1 * τ2 I int * (int * int) est une paire dont la première composante est un entier et la deuxième une paire d’entiers 34/19 4 Les n-uplets Les tableaux # let t = [| 5; 1; 3 |] ;; val t : int array = [| 5; 1; 3 |] # t.(0) <- 10 ;; - : unit = () # t.(0) ;; - : int = 10 # Array.length t ;; - : int = 3 Attention également à ne pas confondre les types des fonctions suivantes : # let val f # let val f I I f : f : x y int (x, int z = x + y + z ;; -> int -> int -> int = <fun> y, z) = x + y + z ;; * int * int -> int = <fun> I int -> int -> int -> int est le type d’une fonction avec trois arguments de type int I I (int * int * int) -> int désigne le type d’une fonction à un argument (un triplet) de type int * int * int 5/19 array est le type “générique” des tableaux t.(i) est l’accès au ième élément d’un tableau t t.(i) <- v affecte la valeur v dans ième case de t L’indexation des cases d’un tableau contenant n éléments se fait de 0 à n − 1 56/19 Les tableaux # let val t # let val t 6 Les tableaux multi-dimensionnels t : t : # let t = [| [|1; 0|]; [|0; 1|] |] ;; val t : int array array = [| [|1; 0|]; [|0; 1|] |] # t.(0).(1) ;; - : int = 0 # t.(1) ;; - : int array = [|0; 1|] # let t = Array.make matrix 2 2 0. ;; val t : float array array = [|[|0.; 0.|]; [|0.; 0.|]|] = Array.make 4 ’a’ ;; char array = [| ’a’; ’a’; ’a’; ’a’ |] = Array.init 5 (fun i -> 2 * i) ;; int array = [|0; 2; 4; 6; 8|] I Array.make n v crée un tableau de n cases, toutes initialisées avec la même valeur v (attention au partage) ; I Array.init n f crée un tableau de taille n initialisé par la fonction f : pour chaque case d’indice i, f i renvoie la valeur à stocker en i. I I I les tableaux multi-dimensionnels sont simplement représentés par des tableaux contenant d’autres tableaux les matrices sont donc de type α array array, où α représente n’importe quel type l’opération .(i) est associative à gauche, c’est-à-dire qu’il faut lire t.(i).(j).(k) comme t.(i) .(j) .(k) 7/19 78/19 8 Les fonctions anonymes # # # - L’ordre supérieur fun x -> x * x ;; : int -> int = <fun> (fun x -> x * x) 4 ;; : int = 16 fun x y -> x * y ;; : int -> int -> int = <fun> I le mot-clé fun permet de créer des valeurs fonctionnelles I les fonctions anonymes s’appliquent comme les fonctions nommées I la déclation let f x y = x * y est donc équivalente à Les fonctions sont des valeurs comme les autres Elles peuvent être I stockées dans une structure de donnée I passées en argument à une autre fonction I retournées comme résultat d’une fonction Les fonctions prenant des fonctions en arguments ou rendant des fonctions en résultat sont dites d’ordre supérieur let f = fun x y -> x * y 9/19 910/19 Fonctions stockées dans des structures de données Fonctions comme arguments # ( (fun x -> x + 1), 4.2 ) ;; - : (int -> int) * float = (<fun>, 4) # let t = [| (fun x -> x * 2); (fun x -> x + 1) |] ;; val t : (int -> int) array = [| <fun>; <fun> |] # t.(0) 5 ;; - : int = 10 # let f = ref (fun x -> x + 2) ;; val f : (int -> int) ref # !f 5 ;; - : int = 7 # f : = (fun x -> x / 4) ;; - : unit = () I 11/19 10 I certaines fonctions prennent naturellement des fonctions en arguments I par exemple, les notations mathématiques telles que la sommation Σni=1 f (i) se traduisent immédiatement si l’on peut utiliser des arguments fonctionnels # let rec if n<=0 else (f val somme # somme ( - : int = les fonctions peuvent être stockées comme des valeurs quelconques 11 12/19 somme (f, n) = then 0 n) + somme (f, n - 1) ;; : (int -> int) * int -> int = <fun> (fun x -> x * x), 10) ;; 385 12 Exemple : la méthode dichotomique (1/2) Exemple : la méthode dichotomique # let rec dichotomie f (a, b) epsilon = if abs float (b -. a) < epsilon then a else let c = (a +. b) /. 2.0 in let bornes = if (f a) *. (f c) > 0. then (c, b) else (a, c) in dichotomie f bornes epsilon Si f est une fonction continue et monotone, on peut trouver un zéro de f sur un intervalle [a, b] par la méthode dichotomique quand f (a) et f (b) sont de signes opposés : I I (2/2) si est la précision souhaitée et que |b − a| < alors on renvoie a val dichotomie : sinon, couper l’intervalle [a, b] en deux et recommencer sur l’intervalle contenant 0 (float -> float) -> (float * float) -> float -> float = <fun> I on peut utiliser cette méthode pour trouver un encadrement de π en le calculant comme zéro de la fonction cos(x/2) # dichotomie (fun x->cos (x/.2.0)) (3.1, 3.2) 1e-10 ;; - : float = 3.14159265356138384 13/19 13 14/19 Fonctions comme résultats I 14 Applications partielles # let plus2 = plus 2 ;; val plus2 : int -> int = <fun> # plus2 10 ;; - : int = 12 # plus2 100 ;; - : int = 102 Les fonctions à plusieurs arguments sont en fait des fonctions d’ordre supérieur qui rendent des fonctions comme résultats # let plus x y = x + y ;; val plus : int -> int -> int I I Il faut lire le type de cette fonction de la manière suivante int -> (int -> int) I I De manière équivalente, on peut écrire la fonction plus de la façon suivante afin de souligner son résultat fonctionnel # let f x = Printf.printf "x = %d" x; (fun y -> Printf.printf "y = %d" y);; # let f1 = f 1 ;; x = 1 val f1 : int -> unit = <fun> # let () = f1 2 ;; y = 2 - : unit = () # let plus x = (fun y -> x+y) ;; val plus : int -> int -> int 15/19 Les fonctions d’ordre supérieur rendant des fonctions en résultats peuvent être appliquées partiellement On peut faire des calculs avant de renvoyer un résultat 15 16/19 16 Fermetures Exemple de fonctions en arguments et en résultat (1/2) # let compteur i = let etat = ref i in fun () -> etat := !etat + 1; !etat ;; val compteur : int -> unit -> int # let f = compteur 0 ;; val f : unit -> int = <fun> # f () ;; - : int = 1 # f () ;; - : int = 2 # let g = compteur 10 ;; val g : unit -> int = <fun> # g() ;; - : int = 11 I I On peut calculer de façon approximative la dérivée f 0 d’une fonction f avec un petit intervalle dx de la manière suivante : # let derive (f, dx) = fun x -> ( f(x +. dx) -. f(x) ) /. dx ;; val derive : (float -> float) * float -> float -> float = <fun> # derive ( (fun x -> x *. x), 1e-10) 1. ;; - : float = 2.000000165480742 Une fermeture est une fonction avec un état interne Dans cet exemple, les fonctions f et g ont chacune leur état 18/19 17 17/19 Exemple : fonctions en arguments et en résultat I (2/2) On peut réécrire la fonction derive de la manière suivante # let derive dx f = fun x -> ( f(x +. dx) -. f(x) ) /. dx ;; val derive : float -> (float -> float) -> float -> float I On fixe le paramètre dx par application partielle # let derivation = derive 1e-10 ;; val derivation : (float -> float) -> float -> float I 19/19 On peut alors définir par exemple la dérivée de la fonction sinus simplement de la manière suivante : # let sin’ = derivation sin ;; val sin’ : float -> float # sin’ 1. ;; - : float = 0.540302247387103307 # cos 1. ;; - : float = 0.540302305868139765 19 18