Programmation fonctionnelle Notes de cours Cours 1 12 Septembre 2012 Sylvain Conchon [email protected] 1/32 Calendrier Les TD commencent la semaine du 17 Septembre Au total : 10 cours de 1h45, 11 TD de 3h Contrôle des connaissances : I partiel : semaine du 22 Octobre (à confirmer), coef. 0, 4 I examen : semaine du 10 Décembre (à confirmer), coef. 0, 6 Retrouver toutes ces informations (et d’autres) sur le web http://www.lri.fr/~conchon/PF/ 2/32 Objectifs de ce cours Programmer, programmer, programmer. . . I acquérir de bons principes de programmation (indépendants du langage de programmation) I programmer les algorithmes vus en cours d’algorithmique I savoir exploiter au mieux les atouts de la programmation fonctionnelle dans vos applications futures 3/32 Le langage Ocaml http://caml.inria.fr I I langage développé à l’INRIA (Institut National de Recherche en Informatique et en Automatique) disponible sur de nombreuses architectures (Linux, Windows, Mac OS X etc.) Références : I I I E. Chailloux, P. Manoury, B. Pagano Développement d’Applications avec Objective Caml http://www.pps.jussieu.fr/Livres/ora/DA-OCAML/ P. Weis, X. Leroy Le Langage Caml http://caml.inria.fr/pub/distrib/books/llc.pdf G. Cousineau, M. Mauny Approche Fonctionnelle de la Programmation http://pauillac.inria.fr/cousineau-mauny/ 4/32 Principe du déroulement de ce cours 1 cours = 1 programme Notes de cours : I un poly d’introduction à OCaml ; I des transparents qui contiennent les nouvelles notions du langage Ocaml nécessaires pour réaliser le programme étudié ; I un listing du code du programme étudié 5/32 Le programme de cette semaine La fractale de Mandelbrot 6/32 L’ensemble de Mandelbrot Cet ensemble est défini comme l’ensemble des points (a, b) du plan pour lesquels la suite récurrente suivante ne diverge pas vers l’infini. x0 y0 xn+1 yn+1 = = = = 0 0 xn2 − yn2 + a 2xn yn + b I on peut démontrer que la suite diverge dès que xn2 + yn2 > 4 I le calcul ne s’arrête pas pour les points appartenant à l’ensemble ; il faut donc fixer le nombre maximal d’itérations devant être fait par le programme On dessinera cet ensemble dans une fenêtre de taille 800 × 800 pour des valeurs de (a, b) dans [−2, 1] × [− 23 , 32 ]. Pour faire joli, on peut afficher les points avec une couleur qui dépend du nombre d’itérations nécessaire pour s’arrêter. 7/32 Les notions abordées avec ce programme I la forme des programmes Ocaml I la boucle de compilation I la boucle d’interaction I les types et expressions élémentaires I les séquences d’instructions I les n-uplets I les déclarations de constantes I les déclarations de fonctions et fonctions récursives I l’inférence de type et les erreurs de typage I les boucles for I la bibliothèque graphique Graphics 8/32 La forme des programmes Ocaml Il s’agit simplement d’une suite de déclarations de la forme : let motif1 . . . motifn = expression I ces déclarations sont évaluées de haut en bas I les motifs peuvent être des identificateurs ou des valeurs comme () (nous verrons cela par la suite quand nous étudierons le filtrage) I l’évaluation d’une déclaration consiste à évaluer l’expression à droite du symbole =, puis à associer le résultat obtenu au motif motif1 Il n’y a donc pas de point d’entrée particulier (fonction principale par ex.) comme dans d’autres langages. 9/32 La boucle de compilation I fichier hello.ml let () = Printf.printf "Hello world!\n" I compilation > ocamlc -o hello hello.ml I exécution > ./hello Hello world! > Remarque : le caractère > est le prompt du terminal 10/32 Options du compilateur ocamlc <options> <fichiers> Options courantes : I I I I I I -o <fichier> fixe le nom de l’exécutable à <fichier> -c compile sans édition de liens (pour fabriquer des modules séparemment) -a construit une bibliothèque -dtypes sauvegarde des informations de typage (visibles dans emacs) -I <dir> ajoute <dir> à la liste des répertoires de bibliothèques et/ou modules -g ajoute des informations de débuggage accessibles à runtime via ocamlrun N’hésitez pas à taper ocamlc -help ou man ocamlc dans votre terminal pour avoir la liste complète des options. 11/32 Bytecode ou code natif Il existe deux versions du compilateur Ocaml. I ocamlc produit des fichiers (modules ou exécutables) en bytecode I ocamlopt génère du code machine directement exécutable par l’ordinateur Avantages et inconvénients de ces formats : I les fichiers en bytecode ont l’avantage de pouvoir être exécutés sur n’importe quelle plate-forme, sans être re-compilés (c’est le format choisi par exemple en Java) I un fichier en langage machine s’exécute plus rapidement 12/32 La boucle d’interaction Ocaml dispose d’un interpréteur (toplevel), très utile pour tester des petits programmes. I exécution du toplevel > ocaml Objective Caml version 3.11.1 # I interaction # let () = Printf.printf "Hello world!\n";; Hello world! - : unit = () Remarque : le caractère # est le prompt du toplevel 13/32 Types et expressions élémentaires Expressions de type int # # # # - 4 + 1 - 2 * 2 ;; : int = 1 5 / 2 ;; : int = 2 1 000 005 mod 2 ;; : int = 1 max int + 1 ;; : int = -1073741824 I int représente les entiers compris entre −230 et 230 − 1 (sur une machine 32 bits) I opérations sur ce type : +, -, *, / (division entière), mod (reste de la division) etc. 14/32 Types et expressions élémentaires Expressions de type float # # # # - 4.3e4 +. 1.2 *. -2.3 ;; : float = 42997.24 5. /. 2. ;; : float = 2.5 1. /. 0. ;; : float = infinity 0. /. 0. ;; : float = nan I float représente les nombres flottants à l’aide d’une mantisse m et d’un exposant n (pour coder le nombre m × 10n ) I les types int et float sont disjoints I opérations sur ce type : +. -. *. /. sqrt cos etc. I on passe d’un entier à un flottant à l’aide de la fonction float et inversement avec truncate 15/32 Types et expressions élémentaires Expressions de type bool # # # # I I I I false || true ;; : bool = true 3 <= 1 ;; : bool = false not (0=2) && 1>=3 ;; : bool = false if 2<0 then 2.0 else (4.6 *. 1.2) ;; : float = 5.52 bool représente les valeurs true (vrai) et false (faux) opérations sur ce type not (non), && (et) et || (ou) les opérateurs de comparaison (=, <, >, <=, >=) retournent des valeurs booléennes dans une conditionnelle de la forme if exp1 then exp2 else exp3 l’expression exp1 doit être de type bool. 16/32 Types et expressions élémentaires Expressions de type char # # # - ’a’ ;; : char = ’a’ int of char ’a’ ;; : int = 97 char of int 100 ;; : char = ’d’ I char représente les caractères I ces valeurs sont encadrées de deux apostrophes ’ I la fonction int of char renvoie le code ASCII d’un caractère (et inversement pour la fonction char of int). 17/32 Types et expressions élémentaires Expressions de type string I I I I I # "hello" ;; - : string = "hello" # "" ;; - : string = "" # "bon" ^ "jour" ;; - : string = "bonjour" # "hello".[1] ;; - : char = ’e’ # string of int 123 ;; - : string = "123" string représente les chaı̂nes de caractères ces valeurs sont encadrées par deux guillemets " l’opérateur ^ permet de concaténer des chaı̂nes l’opération .[i] permet d’accéder au ie caractère d’une chaı̂ne (le premier caractère est à l’indice 0) des fonctions de conversions permettent de convertir des valeurs de types de base en chaı̂nes (et inversement) 18/32 Types et expressions élémentaires Expressions de type unit # () ;; - : unit = () # Print.printf "bonjour\n" ;; bonjour - : unit = () I unit représente les expressions sans réelle valeur (ex. fonctions d’affichage) I une seule valeur a ce type, elle est notée () I c’est l’équivalent du type void en C 19/32 Séquence d’instructions # Print.printf "bonjour\n"; 5 ;; bonjour - : int = 5 I le point virgule ; permet d’évaluer une séquence d’expressions Les expressions de type unit sont identiques à des instructions Étant donnée une séquence d’expressions e1 ;e2 ;· · · ;en I toutes les expressions e1 , . . . , en−1 doivent être de type unit I la valeur et le type de la séquence sont ceux de la dernière expression en 20/32 Les n-uplets Expressions de type produit # # # # # - (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") I le type produit * représente les n-uplets I les n-uplets peuvent être arbitrairement imbriqués I fst et snd permettent d’accéder respectivement à la première et à la deuxième composante d’une valeur de type τ1 * τ2 21/32 Les n-uplets Attention à ne pas confondre les types des expressions suivantes : # # # I (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)) 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 I int * (int * int) est une paire dont la première composante est un entier et la deuxième une paire d’entiers 22/32 Constantes globales # let x = 3 ;; val x : int = 3 I I I I le type est inféré automatiquement par le compilateur le contenu d’une constante n’est pas modifiable la portée est limitée aux déclarations suivantes # let y = 5 + x ;; val y : int = 8 # let z = 10 + z ;; Error: Unbound value z La liaison est statique : la redéfinition ne change pas la valeur des expressions précédentes # let x = 10 ;; val x : int = 10 # y ;; - : int = 8 23/32 Constantes locales # let x = 3 in x + 1;; - : int = 4 I I la portée est limitée à l’expression qui suit le in # x + 2;; Error: Unbound value x le nom de la constante locale masque toute déclaration antérieure de même nom # let y = 2;; val y : int = 2 # let y = 100 in y + 1;; - : int = 101 # y + 3;; - : int = 5 24/32 Fonctions # let f x = x + 2;; val f : int -> int = <fun> # f 4;; - : int = 6 I les types, des arguments et du résultat, sont inférés I la règle de portée du nom de la fonction est identique à celle des constantes (globales ou locales) # let h - : int # h 4;; Error : # let g Error : x = x / 2 in h 6;; = 3 Unbound value h x = x || g (not x);; Unbound value g 25/32 Fonctions à plusieurs arguments # let f x y z = if x then y + 1 else z - 1;; val f : bool -> int -> int -> int = <fun> # f true 2 3;; - : int = 3 I les paramètres ne sont pas entre parenthèses, ni dans les déclarations, ni dans les applications de fonctions Attention : La fonction suivante prend en fait un seul paramètre # let f (x, y, z) = if x then y + 1 else z - 1;; val f : bool * int * int -> int = <fun> # f (true, 2, 3);; - : int = 3 26/32 Opérateurs # ( + );; - : int -> int -> int = <fun> # let ( ++ ) (x1, y1) (x2, y2) = (x1 + x2, y1 + y2);; val ++: int = 3 I un opérateur est simplement une fonction infixe I on manipule cette fonction à l’aide de la notation ( op ) I les noms des opérateurs ne doivent contenir que des symboles +, *, $ etc. 27/32 Fonctions récursives # let rec fact x = if x <= 0 then 1 else x * fact (x - 1);; val fact : int -> int = <fun> # fact 4;; - : int = 24 I l’ajout du mot-clé rec change la portée de l’identificateur : il est alors accessible dans la définition de la fonction 28/32 La boucle for # for i = 1 to 5 do Printf.printf "%d " i done;; - : unit = () 1 2 3 4 5 I I I I i est non modifiable et visible que dans le corps de la boucle il est incrémenté automatiquement à chaque tour de boucle (il peut être décrémenté en utilisant downto à la place de to) l’expression entre do et done doit être de type unit les expressions des bornes ne sont évaluées qu’une seule fois # let f i = Printf.printf "%d " i; i # for i = f 1 to f 5 do Printf.printf "." done;; - : unit = () 1 5 ..... 29/32 L’inférence de type et les erreurs de typage # let x = 3 + "hello";; Error: This expression has type string but an expression was expected of type int # let f x = x - 1;; val f : int -> int = <fun> # f 3 2;; Error: This function is applied to too many arguments # for i = 0 to 5 do i+1 done;; Warning: This expression should have type unit I bien indenter les programmes (utiliser un mode d’indentation automatique) I lire les messages d’erreurs 30/32 La bibliothèque Graphics I ajouter le fichier graphics.cmxa dans la commande de compilation (ocamlopt ... graphics.cmxa ...) I la directive open Graphics permet d’accéder directement aux fonctions de la bibliothèque Graphics I open graph " 400x300" ouvre une fenêtre graphique de 400 pixels de large et 300 de haut ; le pixel avec les coordonnées (0, 0) est en bas à gauche I plot x y affiche un pixel en (x, y ) dans la couleur courante I rgb r v b renvoie une couleur calculée à partir de composantes rouge, vert et bleu (entiers entre 0 et 255) ; les couleurs prédéfinies : white, black, blue, green etc. I set color c fixe la couleur courante à la valeur c I let st = wait next event [Button down] attend un clic de souris ; les coordonnées sont st.mouse x et st.mouse y 31/32 Exercice à faire à la maison I modifier le programme mandelbrot.ml afin de pouvoir zoomer sur une région particulière de la fractale ; laisser l’utilisateur déterminer cette région à l’aide de la souris I afficher les points avec une couleur qui dépend du nombre d’itérations nécessaire pour s’arrêter 32/32