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
h
compose deux fonctionsf
etg
:h f g
est 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})