Diaporama n°12¶

Le langage OCaml - Les bases¶

I. Rappels¶

  • Types de base : int, float, char, string, unit
  • Opérateurs : ...
  • Fonctions prédéfinies
  • Types des fonctions ; t_arg1 -> t_arg2 ... -> t_res
  • Application (appel de fonction) : sans parenthèses
  • Priorité de l'appel de fonction sur les autres opérateurs

Rappels - suite¶

  • Variable globale, locale
  • Fonction globale, locale
    • n paramètres, appel de fonction
    • "0" paramètre = 1 paramètre de type unit : let test () = ...;; test ();;
  • Typage automatique (inférence de type) : c'est le type le plus général qui est conservé
  • Variable de type (polymorphisme) : 'a

II. Fonction récursive¶

on utilise le mot clé rec

  • let rec f arg1 arg2 ... argn = expr : fonction récursive globale

  • let rec f arg1 arg2 ... argn = expr1 in expr2 : fonction récursive locale

III. Expression conditionnelle¶

  • if cond then expr1 else expr2
  • Contraintes de typage ?
  • cond est de type bool
  • expr1 et expr2 sont de même type

Exemple :¶

Définir la fonction (récursive) puissance_entiere de type int -> int -> int telle que puissance_entiere m n calcule $m ^n$

In [2]:
let rec puissance_entiere m n = if n = 0 then 1 else m * puissance_entiere m (n-1);;

puissance_entiere 2 10;;
Out[2]:
val puissance_entiere : int -> int -> int = <fun>
Out[2]:
- : int = 1024

IV. Exercices¶

Exercice 1 :¶

  • Donner le type et valeur des expressions suivantes, ou erreur le cas échéant
  • let plus_un x = x + 1;;
In [1]:
let plus_un x = x + 1;;
Out[1]:
val plus_un : int -> int = <fun>
  • plus_un 7;;
In [2]:
plus_un 7;;
Out[2]:
- : int = 8
  • let apply f x = f x;;
In [3]:
let apply f x = f x;;
Out[3]:
val apply : ('a -> 'b) -> 'a -> 'b = <fun>

La fonction apply a 2 paramètres.

Le premier f est une fonction : a -> b

Le second x est passé en paramètre à f : a

Le résultat f x est donc de type b

  • apply plus_un 4;;
In [4]:
apply plus_un 4;;
Out[4]:
- : int = 5
In [5]:
let plus_un x = x + 1;;
let apply f x = f x;;
Out[5]:
val plus_un : int -> int = <fun>
Out[5]:
val apply : ('a -> 'b) -> 'a -> 'b = <fun>

(a -> b) -> a -> b avec a = int et b = int

  • apply plus_un "4";;
In [6]:
apply plus_un "4";; (* types incorrects : int->int   string*)
File "[6]", line 1, characters 14-17:
1 | apply plus_un "4";; (* types incorrects : int->int   string*)
                  ^^^
Error: This expression has type string but an expression was expected of type
         int

si le premier paramètre d'apply est de type int -> ..., le second paramètre doit être de type int

  • let double_apply f x = f (f x);;
In [7]:
let double_apply f x = f (f x);;
Out[7]:
val double_apply : ('a -> 'a) -> 'a -> 'a = <fun>

Le résutat de f x doit être de même type que le type du paramètre de f.

Donc f : a -> a

  • double_apply plus_un 4;;
In [8]:
double_apply plus_un 4;;
Out[8]:
- : int = 6

Exercice 2 :¶

  • Définir la fonction maximum a b qui renvoie la plus grande valeur des deux arguments

  • Quel est le type de cette fonction ?

  • Tester sur différents arguments, de différents types

NB : cette fonction est prédéfinie en OCaml : c'est la fonction max

In [ ]:
let maximum a b = if a > b then a else b;; (* fonction polymorphe *)
In [9]:
let maximum a b = if a > b then a else b;; (* fonction polymorphe *)
Out[9]:
val maximum : 'a -> 'a -> 'a = <fun>
In [10]:
maximum 17 8;;  (* deux entiers *)
maximum 1.05 1.06;; (* deux flottants *)
maximum "ballon" "voiture";; (* deux chaines de caractères *)
Out[10]:
- : int = 17
Out[10]:
- : float = 1.06
Out[10]:
- : string = "voiture"
In [11]:
maximum 1 1.0;; (* ERREUR : les arguments ne sont pas de même type *)
File "[11]", line 1, characters 10-13:
1 | maximum 1 1.0;; (* ERREUR : les arguments ne sont pas de même type *)
              ^^^
Error: This expression has type float but an expression was expected of type
         int
In [12]:
max;;
Out[12]:
- : 'a -> 'a -> 'a = <fun>

Exercice 3 :¶

  • Définir globalement la fonction fibo n qui calcule le n-ieme terme de la suite de Fibonacci avec deux appels récursifs

  • Calculer le 40e terme de la suite

  • NB : cette fonction est toujours de complexité exponentielle

In [13]:
let rec fibo n = if  n < 2 then n else fibo (n-1) + fibo (n-2);;

fibo 40;;
Out[13]:
val fibo : int -> int = <fun>
Out[13]:
- : int = 102334155
  • Définir fibo n comme une fonction locale pour calculer fibo 8
In [14]:
let rec fibo n = if  n < 2 then n else fibo (n-1) + fibo (n-2) in fibo 8;;
Out[14]:
- : int = 21

NB : les phrases OCaml deviennent de plus en plus longues

En OCaml, l'indentation n'est pas signifiante. On indente pour la lisiblité.¶

Comment indenter pour que le code soit le plus lisible possible ?

V. Conventions d'indentation¶

V.1 Convention : variable globale avec expression courte¶

In [ ]:
let ... = ...;;
In [16]:
let random () = Random.float 1.0;;
Out[16]:
val random : unit -> float = <fun>

V.2 Convention : variable globale avec expressions longues¶

In [ ]:
let ... = 
    ...................;;
In [18]:
let rec fibo n = 
    if  n < 2 then n else fibo (n-1) + fibo (n-2);;
Out[18]:
val fibo : int -> int = <fun>

V.3 Convention : variable locale avec expressions courtes¶

In [ ]:
let ... = ... in expr;;

ou bien

In [ ]:
let ... = ... in
expr;;

NB : dans let x = expr1 in expr2, c'est expr2 qui est le résultat

In [21]:
let f x = x * x in f 4 + f 3 = f 5;;
Out[21]:
- : bool = true
In [22]:
let f x = x * x in 
f 4 + f 3 = f 5;;
Out[22]:
- : bool = true

V.4 Convention : variable locale avec expressions longues¶

In [ ]:
let ... = 
    ...................
in 
expr;;
In [24]:
let rec fibo n = 
    if  n < 2 then n else fibo (n-1) + fibo (n-2) 
in 
fibo 8;;
Out[24]:
- : int = 21

V.5 Convention : variables locales multiples¶

In [ ]:
let .... = .... in
let .... = .... in
expr;;

Exercice 4 :¶

On souhaite réaliser l'expérience suivante. Choisir deux valeurs au hasard $x$ et $y$ de l'intervalle $[-1;1]$. Déterminer si le point $M(x,y)$ est dans le cercle de centre $O(0,0)$ et de rayon 1

  • Random.float x renvoie un float aléatoire de l'intervalle $[0,x]$

  • Définir deux variables locales x et y

  • Quelle est la condition à vérifier ?

en respectant les conventions d'indentation

In [26]:
let x = Random.float 2.0 -. 1.0 in
let y = Random.float 2.0 -. 1.0 in
x**2.0 +. y**2.0 < 1.0;;
Out[26]:
- : bool = true

Exercice 4 - suite :¶

  • définir la fonction globale experience (), qui réalise l'expérience précédente et renvoie un booléen

en respectant les conventions d'indentation

In [27]:
let experience () =
    let x = Random.float 2.0 -. 1.0 in
    let y = Random.float 2.0 -. 1.0 in
    x**2.0 +. y**2.0 < 1.0;;

experience ();;
experience ();;
Out[27]:
val experience : unit -> bool = <fun>
Out[27]:
- : bool = true
Out[27]:
- : bool = true

V.6 Convention : expression conditionnelle courte¶

In [ ]:
if ... then ... else ...

Exercice 5 :¶

  • Ecrire la fonction globale v_abs x : valeur absolue d'un entier.

  • Tester sur -5.

NB : cette fonction est prédéfinie en OCaml. C'est la fonction abs

In [2]:
let v_abs x = if x > 0 then x else -x;;

v_abs -5;; (* lu :  (v_abs) - (5) *)
Out[2]:
val v_abs : int -> int = <fun>
File "[2]", line 3, characters 0-5:
3 | v_abs -5;; (* lu :  (v_abs) - (5) *)
    ^^^^^
Error: This expression has type int -> int
       but an expression was expected of type int
In [30]:
let v_abs x = if x > 0 then x else -x;;

v_abs (-5);; (* parentèses indispensables *)
Out[30]:
val v_abs : int -> int = <fun>
Out[30]:
- : int = 5

V.7 Convention : expression conditionnelle longue¶

In [ ]:
if ... then 
    ... 
else 
    ...

Exercice 6 :¶

  • Ecrire la fonction maximum3 a b c qui renvoie la plus grande valeur des 3 paramètres

  • Tester toutes les branches avec la fonction assert : bool -> unit qui lève une exception si la condition n'est pas vérifiée

In [32]:
let maximum3 a b c =
    if a < b then 
        if b < c then c else b
    else 
        if a < c then c else a;;

(* attention, il y a 4 cas *)
assert (maximum3 1 2 3 = 3);;
assert (maximum3 1 2 0 = 2);;
assert (maximum3 2 1 3 = 3);;
assert (maximum3 2 1 0 = 2);;
Out[32]:
val maximum3 : 'a -> 'a -> 'a -> 'a = <fun>
Out[32]:
- : unit = ()
Out[32]:
- : unit = ()
Out[32]:
- : unit = ()
Out[32]:
- : unit = ()

V.8 Convention : expressions conditionnelles cas multiples¶

In [ ]:
if ... then 
    ... 
else if ... then
    ...
else
    ...

Exercice 7 :¶

  • Ecrire une fonction signe x qui renvoie "nul", "positif" ou "négatif" selon le signe de l'entier x
In [34]:
let signe x =
    if x = 0 then 
        "nul"
    else if x > 0 then
        "positif"
    else 
        "négatif";;
Out[34]:
val signe : int -> string = <fun>

VI. Les fonctions anonymes¶

"où l'on voit qu'une fonction est une valeur comme une autre ..."

Définition d'une fonction anonyme¶

fun arg1 arg2 ... argn -> expr : désigne une fonction anonyme (sans nom) ayant n paramètres

In [65]:
fun x y -> (3. *. x +. 7.) /. (4. -. y *. x);;
Out[65]:
- : float -> float -> float = <fun>

on peut appliquer une fonction anonyme¶

In [36]:
(fun x y -> x + y) 4 5;;
Out[36]:
- : int = 9

on peut définir une variable ayant pour valeur une telle fonction¶

In [37]:
let f = fun x y -> x + y;;
Out[37]:
val f : int -> int -> int = <fun>
In [38]:
f 4 5;;
Out[38]:
- : int = 9

NB : il y a équivalence entre les deux écritures

In [39]:
let f = fun x y -> x + y;;

let f x y = x + y;;
Out[39]:
val f : int -> int -> int = <fun>
Out[39]:
val f : int -> int -> int = <fun>

Ces deux ecritures désignent une fonction prenant deux paramètres et calculant la somme

on peut écrire une fonction renvoyant une autre fonction !!¶

Exercice 8¶

Ecrire une fonction cree_rand a b qui renvoie une fonction qui renvoie un float aléatoire de l'intervalle $[a,b]$

NB : Random.float x renvoie un float aléatoire de l'intervalle $[0,x]$

In [3]:
let cree_rand a b = (fun () -> a +. Random.float (b -. a));;
Out[3]:
val cree_rand : float -> float -> unit -> float = <fun>

Lire val cree_rand : float -> float -> (unit -> float) = <fun>

La valeur renvoyée est une fonction de type (unit -> float)

In [4]:
let f_rand_0 = cree_rand (-1.) 1.;;
let f_rand_1 = cree_rand (-10.) 10.;;
f_rand_0 ();;
f_rand_0 ();;
f_rand_1 ();;
f_rand_1 ();;
Out[4]:
val f_rand_0 : unit -> float = <fun>
Out[4]:
val f_rand_1 : unit -> float = <fun>
Out[4]:
- : float = 0.852077776721942293
Out[4]:
- : float = -0.316722822803535808
Out[4]:
- : float = 1.95645891706211827
Out[4]:
- : float = 7.33763182029579

ATTENTION : les fonctions sont des valeurs comme les autres¶

  • une définition let v = ... peut être une définition de fonction

  • utiliser des noms de variables significatifs, des commentaires si nécessaire

VII. Les n-uplets (tuples)¶

  • (expr1, expr2, ..., exprn) désigne un n-uplet

NB : les types des expressions composant le n-uplet ne sont pas forcément égaux

NB : type : T1 * T2 * ... * Tn avec expri : Ti

NB : non modifiable

In [42]:
(2, 3);; (* un couple d'entiers *)
Out[42]:
- : int * int = (2, 3)
In [9]:
(2, 'a');; (* un couple entier, caractère *)
Out[9]:
- : int * char = (2, 'a')

Attention : les parenthèses ne sont pas indispensables

ce qui crée parfois des confusions ...

In [7]:
3,14;; (* Attention, les parenthèses ne sont pas indispensables *)
Out[7]:
- : int * int = (3, 14)

NB : mettre systématiquement des parenthèses (dans un premier temps)

In [43]:
(2, 3.14, "coucou", fun x -> 2 * x);; (* un quadruplet de ... *)
Out[43]:
- : int * float * string * (int -> int) = (2, 3.14, "coucou", <fun>)

NB : n-uplet pour $n$ supérieur ou égal à 2

  • 1-uplet ? : c'est le parenthésage habituel

  • 0-uplet ? : c'est la valeur ()

In [44]:
("hello");;
();;
Out[44]:
- : string = "hello"
Out[44]:
- : unit = ()

On peut définir un couple de variables¶

  • let (x, y) = expr : expr doit être de type couple

  • NB : un peu comme en Python. Permet de récupérer directement les deux composantes d'un couple

On peut définir un argument sous forme de couple¶

  • let f (x, y) = ... : la fonction f prend en argument un couple dont la première composante est appelée x et la deuxième y

  • NB : ne pas confondre avec la notation C / Python de fonction à deux paramètres

  • NB : se généralise aux n-uplets

Exercice 9¶

  • Ecrire une fonction pointAlea x qui renvoie un couple de coordonnées aléatoires de $[0,x]²$

  • Ecrire une fonction distance_origine qui prend un couple de coordonnées et renvoie sa distance à l'origine

In [68]:
let pointAlea x =
    let px = Random.float x in
    let py = Random.float x in
    (px, py);;

let p1 = pointAlea 10.;; (* une variable de type couple de float *)

let (px, py) = pointAlea 10.;; (* un couple de variables de type float *)
Out[68]:
val pointAlea : float -> float * float = <fun>
Out[68]:
val p1 : float * float = (3.9650094694195066, 1.90217751234253374)
Out[68]:
val px : float = 5.67660068680959107
val py : float = 4.035922677801147
In [69]:
let distance_origine (x, y) = sqrt (x**2. +. y**2.);;

distance_origine p1;;

distance_origine (px, py);;
Out[69]:
val distance_origine : float * float -> float = <fun>
Out[69]:
- : float = 4.39767886288300769
Out[69]:
- : float = 6.96508917521353066

Exercice 10¶

  • Ecrire la fonction premiere_composante qui prend un couple en argument et renvoie sa première composante.

  • Quel est le type de cette fonction ?

NB c'est la fonction fst en OCaml (snd pour la seconde composante)

In [47]:
let premiere_composante (x, y) = x;; (* fonction polymorphe *)

premiere_composante (7, "toto");;
Out[47]:
val premiere_composante : 'a * 'b -> 'a = <fun>
Out[47]:
- : int = 7
In [48]:
fst;;
snd;;
Out[48]:
- : 'a * 'b -> 'a = <fun>
Out[48]:
- : 'a * 'b -> 'b = <fun>

VIII. Fonctions "Curryfiées"¶

Quelle(s) différence(s) entre les deux fonctions suivantes :

  • let sumC x y = x + y;;

  • let sumD (x, y) = x + y;;

Leurs types !

In [49]:
let sumC x y = x + y;; (* fonction curryfiée *)

let sumD (x, y) = x + y;; (* fonction décurryfiée *)
Out[49]:
val sumC : int -> int -> int = <fun>
Out[49]:
val sumD : int * int -> int = <fun>
  • let sum x y = x + y;; : fonction "curryfiée" ayant deux arguments

  • let sum (x, y) = x + y;; : fonction "décurryfiée" ayant un seul argument de type couple

NB : de Haskell Curry, mathématicien, logicien qui a posé les bases des langages fonctionnels

Application partielle¶

En OCaml, lorsqu'une fonction a 2 arguments, il est possible de ne passer que le premier

Avantage à la fonction curryfiée :

  • on peut lui passer seulement le premier argument : application partielle
  • on peut lui passer les deux arguments : application (totale)

NB : la fonction décurryfiée n'a qu'un seul argument :

  • on ne peut lui passer que x et y simultanément
In [10]:
let sumC x y = x + y;; (* fonction curryfiée *)

let f = sumC 3;; (* qu'est ce que f ??? *)
Out[10]:
val sumC : int -> int -> int = <fun>
Out[10]:
val f : int -> int = <fun>
In [51]:
f 4;; (* f est la fonction qui ajoute 3*)
f 5;;
Out[51]:
- : int = 7
Out[51]:
- : int = 8

NB : pour une fonction à $n$ arguments, on peut passer - uniquement le premier - uniquement les deux premiers - ...

Par exemple, pour f:'a->'b->'c->'d :

  • f x (avec x:a) est de type b->'c->'d : application partielle

  • f x y (avec x:a et y:b) est de type c->d : application partielle

  • f x y z (avec x:a, y:b et z:c) est de type d : application (totale)

A suivre ...¶