marwan.burelle@lse.epita.fr http://wiki-prog.kh405.free.fr
Plan 1 2 3 4
Objective Caml Langage fonctionnel de la famille ML Langage fortement typée, doté d un typage statique Le système de type propose un mécannisme d inférence (type reconstruction) Le système de type est polymorphe Le langage dispose de l ordre supérieur Le langage propose des extensions impératives Le langage propose un sous-langage de modules très évolué (modules imbriqués, foncteurs... ) Le langage propose un couche objet
Pas de notion variables Itérations au travers de la récursion Les fonctions sont le point centrale du langage Généricité au travers des fonctions de première classe et du polymorphisme Pas d instruction : tout est expression et renvoie une valeur Pas d effet de bord Les opérations sur une structure de donnée travaille toujours en renvoyant une copie.
Architecture Générale En règle général, un programme est composé d une série de définition et de bloc de code à exécuter. Tout est exécuté : il n y a pas de véritable différence entre une définition et un bloc de code exécuté. (* Exemple de programme *) (* Definition de fonction *) let f x = x (* Blocs de code *) let y = begin print_string "Definition de la constante y\n"; 10 end let toto = (print_int y; print_int f y; print_newline ())
Point d entrée Il n y a pas de notion de point d entrée puisque tout le code est exécuté. Attention, exécuter ne signifie pas que le résultat sera visible! Les définitions ne sont pas différentiées des blocs de code simples, le corps de la définition est exécuté et sa valeur résultat est associée au symbole définie. On utilise généralement le motif universel " " pour indiquer que le résultat ne sera pas réutilisé (pas de symbole créé.)
Organisation Concrète En règle général, un programme se compose comme suit : Une série de définition de constantes ; Une série de définition de fonctions ; Une fonction particulière qui servira de point d entrée ; Un bloc de code appelant la fonction principale. (* Constantes *) let pi = acos (-1.) (* Fonctions *) let surface_cercle r = 2. *. pi *. r *. r (* Point d entree *) let main () = let r = int_of_string (Sys.argv.(1)) in print_float (surface_cercle r) (* Appel du point d entree *) let _ = main ()
Compilation v.s. Interprétation L interpréteur affiche un résultat pour toutes les phrases entrées : # 1+1 ;; - : int = 2 Les phrases composées uniquement d une expression, bien qu elles soient exécutées, n ont pas d effet visisble dans un programme compilé : # echo "1+1;;" > toto.ml # ocamlc -o toto toto.ml #./toto # On évitera l utilisation des ;; Un bloc de code qui ne renvoie rien (affichage, bloc impératif... ) sera introduit par une construction : let _ =...
Un programme complet (* Exemple de programme en *) (* Les constantes *) let pi = acos (-1.) (* Les fonctions *) let surface r = 2. *. pi *. r *. r (* Fonction principale *) let main () = begin print_string "Rayon du cercle?\n"; let r = read_float () in print_string "Surface du cercle de rayon "; print_float r; print_string " = "; print_float (surface r); print_newline (); end (* Appel *) let _ = main ()
Typage et Inférence Le typage doit être respecté est capable de déduire tout seul le type des fonctions. # 1 + true ;; This expression has type bool but is here used with type int # let f x = x + 1;; val f : int -> int = <fun > # let f x = x;; val f : a -> a = <fun > # let f g x = (x+1,g x);; val f : (int -> a) -> int -> int * a = <fun > # f string_of_int 3;; - : int * string = (4, "3")
Récursivité Fichier fact.ml : let rec fact = function 0 1 -> 1 x when x>0 -> x * fact (x-1) _ -> assert false let main () = begin let x = int_of_string (Sys.argv.(1)) in print_int (fact x); print_newline (); end let _ = main () Compilation et exécution : $ ocamlc -o fact fact.ml $./fact 6 720 $./fact 10 3628800 $./fact 12 479001600 $
Listes a list Constructeurs : [] liste vide :: ajout en tête Opération : @ concaténation # [];; - : a list = [] # 1::[];; - : int list = [1] # [1;2];; - : int list = [1; 2] # [1;2]@[3;4];; - : int list = [1; 2; 3; 4] # let rec length = function [] -> 0 _::t -> 1 + length t;; val length : a list -> int = <fun > # length [1;2;3];; - : int = 3 #
Ordre Supérieur # let rec map f = function [] -> [] h::t -> (f h)::map f t;; val map : ( a -> b) -> a list -> b list = <fun > # let rec fold f a = function [] -> a h::t -> fold f (f a h) t;; val fold : ( a -> b -> a) -> a -> b list -> a = <fun > # map float_of_int [1;2;3];; - : float list = [1.; 2.; 3.] # fold (+) 0 [1;2;3];; - : int = 6 # fold (fun a x -> x::a) [] [1;2;3;4;5;6;7];; - : int list = [7; 6; 5; 4; 3; 2; 1] # Fichier print list.ml : let rec iter f = function [] -> () h::t -> f h; iter f t let maliste = [1;2;3;4;5;6;7] let main () = iter (fun x -> Printf.printf " %i;" x) maliste; print_newline() let _ = main () $ ocamlc -o print_list print_list.ml $./print_list 1; 2; 3; 4; 5; 6; 7;
Types Sommes type a tree = Leaf of a Node of a tree * a * a tree let rec find x = function Leaf c Node(_,c,_) when x = c -> true Node(fg,_,fd) -> (find x fg) (find x fd) _ -> false let rec list_of_tree = function Leaf c -> [c] Node(fg,c,fd) -> (list_of_tree fg)@[c]@(list_of_tree fd) let arbre = Node(Node(Leaf 1,2,Leaf 3),4, Node(Leaf 5,6,Leaf 7)) let main () = begin List.iter (fun x -> Printf.printf "%i;" x) (list_of_tree arbre); print_newline (); print_string (if (find 5 arbre) then "trouve!\n" else "pas trouve!\n"); end let _ = main ()
Algortithmes et programmation fonctionnelle Prennons le cas de listes triées d entiers : elem = 0 elem = 3 elem = 6 Nous allons insérer de manière fonctionnelle la valeur 2 dans la liste : elem = 0 elem = 3 elem = 6 elem = 0 elem = 2 Si les choses sont faites correctement, la fin de la liste (qui n est pas modifiée) est partagée entre l ancienne liste (qui demeure inchangée) et la nouvelle. Pour plus de détail référez vous aux slides du premier cours.
Essayons de faire des boucles (* Boucles qui ne marchent pas... *) let _ = let x = 10 in while (x<0) do print_int x; let x = x - 1 in (*? *) () done La construction let x =... in... définie un nouveaux symbole x à chaque fois. On voudrait modifier la valeur de x à chaque tour de boucle, ce qui est incohérent avec la programmation fonctionnelle.
Données modifiables Pour faire de la programmation impérative, il faut pouvoir modifier certaines données. Comment peut-on être fonctionnel et avoir des variables? Idée : la donnée manipulée reste constante, mais on dispose d un mécannisme d accès pour la modifier. On parle de donnée muable (terme horrible en français, mais modifiable est un peu trop faible.) En, il existe plusieurs sortes de données muable : les références (pointeurs statiques), les champs d enregistrements, les (cases des) tableaux, les chaînes de caractères et les propriétés des objets.
Les références Une référence est une sorte de pointeur, la valeur d une référence est immuable, mais cette valeur est une adresse sur une valeur que l on pourra modifier. Le polymorphisme sur les références est limité (les changements de valeur référencée doivent préserver le type d origine.) Une référence pointe toujours sur une valeur (pas de problème de pointeur null.) # let r = ref 0;; val r : int ref = {contents = 0} #!r;; - : int = 0 # (r := 2;!r);; - : int = 2 # r := "a" ;; This expression has type string but is here used with type int #
Polymorphisme et références # let f x = x ;; val f : a -> a = <fun > # let r = ref f ;; val r : ( _a -> _a) ref = {contents = <fun >} #!r;; - : _a -> _a = <fun > (* _a is not a polymorphic type variable *) # (!r) 1;; - : int = 1 #!r;; - : int -> int = <fun > (* r is now a reference to a int -> int function *) # f true ;; - : bool = true # (!r) true ;; This expression has type bool but is here used with type int # r := fun x -> x + 1 ;; - : unit = () #!r 1;; - : int = 2 #
Les tableaux # let t = [ 1; 2; 3 ];; val t : int array = [ 1; 2; 3 ] # t.(0);; - : int = 1 # t.(0) <- 2;; - : unit = () # t.(0);; - : int = 2 # let f x = Array.make 10 x;; val f : a -> a array = <fun > # f true ;; - : bool array = [ true; true; true; true; true; true; true; true; true; true ] # let t2 = f (ref 0);; val t2 : int ref array = [ {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0}; {contents = 0} ] # t2.(0) := 1 ;; - : unit = () # t2;; - : int ref array = [ {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1}; {contents = 1} ] #
Les enregistrements # type r = { a : int; mutable b : string } ;; type r = { a : int; mutable b : string; } # let x = { a = 0; b = "" };; val x : r = {a = 0; b = ""} # x.b <- "bonjour" ;; - : unit = () # x;; - : r = {a = 0; b = "bonjour"} # let y = { x with a = 1 };; val y : r = {a = 1; b = "bonjour"} # x.b <- "bonjour2" ;; - : unit = () # y;; - : r = {a = 1; b = "bonjour"} # type r = { a : int ref; b : int };; type r = { a : int ref; b : int; } # let x = {a = ref 0; b = 1 };; val x : r = {a = {contents = 0}; b = 1} # let y = { x with b = 2 };; val y : r = {a = {contents = 0}; b = 2} # x.a := 1; y ;; - : r = {a = {contents = 1}; b = 2}
Les chaînes de caractères # let s = "bonjour ";; val s : string = "bonjour " # s.[7] = \n ;; - : bool = false # s.[7] <- \n ;; - : unit = () # s;; - : string = "bonjour\n" let maj s = for x = 0 to (String.length s) - 1 do s.[x] <- Char.uppercase (s.[x]) done let main () = let s = String.copy Sys.argv.(1) in maj s; print_string s; print_newline () ; exit 0 let _ = main ()
Le retour des boucles let fibbo n = let i = ref 1 in let fib1 = ref 1 in let fib2 = ref 1 in while (!i < n) do fib1 :=!fib1 +!fib2; fib2 :=!fib1 -!fib2; i :=!i + 1 done ;!fib1 let rec fib = function 0 1 -> 1 n -> (fib (n-1)) + (fib (n-2)) let main () = let i = read_int () in Printf.printf "par boucle: %i, par rec: %i\n" (fibbo i) (fib i); exit 0 let _ = main ()