Fonctions 
Utiliser une fonction¶
OCaml est un langage fonctionnel, ce qui signifie que :
- Les fonctions y occupent une place importante et peuvent être manipulées un peu comme des variables
- Les fonctions sont censées ne pas effectuer d'effet de bord, c'est à dire d'action sur l'extérieur de la fonction (pas de modification de variable globale, pas d'écriture dans un fichier...)
Pour utiliser une fonction f sur une valeur x, on écrira simplement f x (et non pas f(x)).
Un certain nombre de fonctions sont déjà définies en OCaml. Par exemple, la racine carrée :
sqrt 2.0 (* renvoie une approximation de racine de 2 *)
- : float = 1.41421356237309515
Chaque fonction possède une signature, qui donne les types des paramètres (valeurs en entrée de la fonction) et le type de la valeur de retour.
sqrt
- : float -> float = <fun>
float -> float signifie que sqrt est une fonction qui prend un flottant en entrée et renvoie un flottant. On ne peut donc pas l'appliquer sur un entier :
sqrt 2 (* erreur : on donne un entier à sqrt qui attend un flottant *)
File "[3]", line 1, characters 5-6:
1 | sqrt 2 (* erreur : on donne un entier à sqrt qui attend un flottant *)
^
Error: This expression has type int but an expression was expected of type
float
Hint: Did you mean `2.'?
Définir une fonction¶
En OCaml, une fonction se définie de la façon suivante :
let nom_fonction nom_argument = ...
où ... est le corps de la fonction, c'est à dire ce qui est exécuté lorsqu'on utilise la fonction.
La valeur renvoyée par la fonction est celle de la dernière instruction (pas besoin de return).
Définissons par exemple la fonction $f: x \longmapsto 2x$ :
let f x = 2*x
val f : int -> int = <fun>
OCaml nous dit que f est de type int -> int, ce qui signifie que f prend un entier en entrée et renvoie un entier en sortie. Ceci est similaire à la notation mathématique $f : \mathbb{N} \longrightarrow \mathbb{N}$.
On peut ensuite utiliser f et récupérer la valeur de retour :
f 3
- : int = 6
Notons que x est une variable muette : elle n'existe qu'à l'intérieur de f, n'a aucun rapport avec une variable x définie précédemment et la fonction suivante définit exactement la même fonction :
let f y = 2*y (* peu importe le nom de la variable muette y *)
- : int = 6
Maintenant que f est définie, on peut calculer $f(3)$ :
f 3
- : int = 6
Exercice
définir la fonction $f : x \longmapsto \frac{1}{\sqrt{1 + x^2}}$ en OCaml.
Solution
let f x = 1./.(1. +. x**2.)**0.5
val f : float -> float = <fun>
Comme pour les variables, il est possible d'utiliser in pour spécifier la portée d'une fonction $g$
let g x = x + 1 in
g 0 (* g est utilisable seulement dans le in *)
- : int = 1
Exercice
Donner la valeur de l'expression suivante :
let h x = f x + 1 in
h 3
Solution
let h x = f x + 1 in (* x est remplacé par 3, f x est remplacé par 6 *)
h 3
- : int = 7
Fonctions anonymes¶
Quand on a besoin d'utiliser une fonction une seule fois, on peut définir une fonction anonyme (sans nom) avec fun. C'est l'équivalent de lambda en Python.
fun x -> x*2 (* définition d'une fonction anonyme *)
- : int -> int = <fun>
(fun x -> x*2) 3 (* applique une fonction anonyme sur la valeur 3 *)
- : int = 6
Remarque : les deux définitions suivantes sont en fait complètement équivalentes.
let f x = ...
let f = fun x -> ...
Par exemple, on peut définir la fonction $f : x \longmapsto 2 \sqrt{x}$ comme ceci :
let f = fun x -> 2.0*.x**0.5
val f : float -> float = <fun>
Remarque : On peut aussi définir une fonction avec function x -> ... mais fun est légèrement plus simple d'utilisation.
Fonctions de plusieurs variables¶
Il est possible de définir des fonctions avec plusieurs paramètres, par exemple :
let sum x y = x + y
val sum : int -> int -> int = <fun>
sum 3 4 (* renvoie 3 + 4 *)
- : int = 7
Le type de sum est int -> int -> int, ce qui peut paraître étrange. C'est équivalent à int -> (int -> int), ce qui signifie que sum prend en entier en argument et renvoie une valeur de type int -> int (c'est à dire une fonction).
En effet :
sum 3
- : int -> int = <fun>
sum 3 est une fonction qui prend en argument un entier y et qui renvoie 3 + y, ce qu'on peut vérifier :
let f = sum 3 in (* f est une fonction *)
f 4 (* renvoie sum 3 4, c'est à dire 7 *)
- : int = 7
En fait, OCaml transforme automatiquement une fonction de plusieurs variables en une suite de fonctions à une variable (c'est ce qu'on appelle la curryfication) :
let sum = fun x -> (fun y -> x + y) (* OCaml transforme la définition de sum ci-dessus en celle-ci *)
val sum : int -> int -> int = <fun>
(sum 2) 3 (* le calcul effectué par OCaml lorsqu'on écrit sum 2 3 *)
- : int = 5
La possibilité d'appliquer une fonction seulement sur certains arguments s'appelle l'application partielle de fonction. C'est un des avantages d'OCaml par rapport à Python.
De la même façon, une fonction OCaml à 3 arguments sera de type ... -> ... -> ... -> ....
Une fonction peut aussi avoir aucune valeur en entrée. Dans ce cas, on lui donne l'argument () (de type unit). C'est le cas par exemple de print_newline, qui saute une ligne :
print_int 0;
print_newline ();
print_int 1;
print_newline ();
0 1
- : unit = ()
Polymorphisme¶
Reprenons notre 1er exemple de fonction :
let f x = 2*x
val f : int -> int = <fun>
OCaml sait que l'argument x de f est un int car on utilise l'opérateur * qui ne s'utilise que sur des entiers. Mais dans certaines fonctions, il n'y a pas de contrainte de type :
let id x = x
val id : 'a -> 'a = <fun>
Cette fonction id (pour identité) renvoie son argument sans le modifier. Comme aucune opération n'est appliquée sur x, il n'y a pas de contrainte sur son type. OCaml utilise alors 'a pour désigner le type quelconque de x.
Notons que le type de retour de id est 'a également : OCaml nous dit que id renvoie une valeur du même type que l'argument.
Exercice
donner le type des fonctions suivantes
let f x = 42
let f x y = y
let g x y f = x + f y
Solution
let f x = 42;;
(* x est quelconque ('a) et le type de retour est int *)
(* donc f est de type 'a -> int *)
val f : 'a -> int = <fun>
let f x y = y;;
(* x est quelconque ('a), y aussi ('b) mais le type de retour est le même que y ('b) *)
(* donc f est de type 'a -> 'b -> 'b *)
val f : 'a -> 'b -> 'b = <fun>
let g x y f = x + f y;;
(* x est un int, à cause du + *)
(* y est quelconque ('a) *)
(* f est une fonction qui prend un y (de type 'a) et renvoie un int, à cause du + *)
(* donc f est de type int -> 'a -> ()*)
Fonction comme argument¶
Il est possible d'utiliser une fonction en argument d'une autre fonction. Par exemple, la fonction suivante évalue une autre fonction en la valeur 0 :
let eval f =
f 0
val eval : (int -> 'a) -> 'a = <fun>
let f x = 3*x + 7 in
eval f
- : int = 7
Exercice
- On définit une fonction
h:Donner la valeur de l'expression :let h f g x = f (g x)
h (fun x -> x*x) (fun x -> x + 1) 3
- Donner le type de
h. - À quoi sert
h? Comment cette opération s'appelle-t-elle mathématiquement?
Solution
(* 1. et 2. *)
let h f g x = f (g x);;
h (fun x -> x*x) (fun x -> x + 1) 3;;
val h : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun>
- : int = 16
Solution
hcompose deux fonctionsfetg:h f gest une fonction équivalent à $f \circ g$.
Variable locale à une fonction¶
Il est possible de définir une variable dans une fonction :
let pow4 x = (* je saute une ligne ici pour plus de lisibilité *)
let y = x*x in (* y est utilisable seulement dans pow4 *)
y*y (* renvoie x puissance 4 *)
val pow4 : int -> int = <fun>
pow4 2 (* test de notre fonction *)
- : int = 16
On peut aussi définir une fonction à l'intérieur d'une fonction. Par exemple, on peut définir $f: x \longmapsto 2x + \sqrt{2(x + 1)}$ en utilisant une fonction locale $g : y \longmapsto 2y$ :
let f x =
let g y = 2.*.y in (* g n'est utilisable que dans f *)
g x +. (g (x +. 1.))**0.5
val f : float -> float = <fun>
f 1.
- : float = 4.
Exercice
Écrire une fonction swap qui échange les valeurs de 2 références en argument.
swap doit être de type 'a ref -> 'a ref -> unit, ce qui signifie que swap a deux références en argument, sur des valeurs de même type 'a, et ne renvoie pas de valeur.
On rappelle les opérations sur les références :
- Définir une référence (locale) :
let a = ref 5 in ... - Obtenir la valeur d'une référence :
!a - Modifier une référence :
a := 7
Remarque importante : Lorsque l'on modifie une référence (ou un autre objet impératif, comme un tableau) qui est l'argument d'une fonction, on la modifie aussi à l'extérieur de la fonction. C'est ce qu'on appelle un passage par référence.
Solution
let swap x y =
let tmp = !y in
y := !x;
x := tmp;;
let x = ref 1 in
let y = ref 2 in
swap x y;
x, y;;
val swap : 'a ref -> 'a ref -> unit = <fun>
- : int ref * int ref = ({contents = 2}, {contents = 1})