Types construits
En OCaml, on peut définir nos propres types.
Type somme (variant)¶
On peut définir un type somme (variant) en énumérant tous les cas possibles pour avoir ce type. Ceci revient à prendre l'union de plusieurs types. Chaque cas doit être identifié par un nom appelé constructeur, qui doit commencer par une majuscule (il est interdit de commencer un nom de variable avec une majuscule, pour ne pas confondre) :
type matiere = Math | Physique | Info
type matiere = Math | Physique | Info
On vient de définir un type matiere
qui contient trois valeurs Math, Physique, Info.
Math;; (* c'est une valeur de type matiere *)
let a = Info;; (* qu'on peut stocker dans une variable *)
- : matiere = Math
val a : matiere = Info
Pour traiter une variable de ce type, on utilise un match
:
let heures m = match m with
| Math -> 12.
| Physique -> 6.5
| Info -> 4.;;
heures Info
val heures : matiere -> float = <fun>
- : float = 4.
Les constructeurs utilisés ci-dessus sont des constantes, mais il est possible d'ajouter un paramètre avec of ...
:
type zbarre = Infini | MoinsInfini | Entier of int
type zbarre = Infini | MoinsInfini | Entier of int
Dans cet exemple, zbarre
est censé représenter $\overline{\mathbb{Z}} = \mathbb{Z} \cup \{-\infty, \infty\}$. Entier
est un constructeur qui dépend d'un paramètre entier. Pour obtenir une valeur à partir du constructeur Entier
, on l'utilise comme une fonction :
Entier 10 (* valeur de type zbarre *)
- : zbarre = Entier 10
Écrivons une fonction pour augmenter une valeur de type zbarre
de 1 :
let add1 n = match n with
| Infini -> Infini
| MoinsInfini -> MoinsInfini
| Entier i -> Entier (i + 1);;
add1 (Entier 10);;
add1 Infini
val add1 : zbarre -> zbarre = <fun>
- : zbarre = Entier 11
- : zbarre = Infini
Écrivons une fonction pour additionner deux valeurs de type zbarre
. Pour pouvoir utiliser un opérateur infixe, on peut définir (+!)
(liste de tous les symboles autorisés comme opérateur infixe):
let (+!) n1 n2 = match n1, n2 with
| Infini, Infini -> Infini
| MoinsInfini, MoinsInfini -> MoinsInfini
| Infini, Entier(_) | Entier(_), Infini -> Infini
| MoinsInfini, Entier(_) | Entier(_), MoinsInfini -> MoinsInfini
| _ -> failwith "Indetermine"
val ( +! ) : zbarre -> zbarre -> zbarre = <fun>
Infini +! Infini;;
Infini +! MoinsInfini
- : zbarre = Infini
Exception: Failure "Indetermine".
Raised at file "stdlib.ml", line 29, characters 22-33
Called from file "toplevel/toploop.ml", line 208, characters 17-27
Exercice
Définir -!
, *!
, /!
pour des valeurs de type zbarre
. Définir aussi ~-
qui permet d'avoir d'appliquer - sur un seul élément (opérateur unaire).
Il est possible d'utiliser 'a
(n'importe quel type) pour un paramètre de constructeur, à condition de le mettre dans le type
(on appelle alors ceci un type polymorphe). Par exemple, on pourrait redéfinir list
:
type 'a liste = Vide | Cons of 'a * 'a liste
type 'a liste = Vide | Cons of 'a * 'a liste
Cons
est alors un constructeur ayant comme paramètre un couple : premier élément de la liste et reste de la liste.
Remarque : liste
est un type récursif (on utilise le type liste
dans la définition de liste
)
let l = Cons(1, Cons(2, Cons(3, Vide))) (* équivalent de 1::2::3::[]) *)
val l : int liste = Cons (1, Cons (2, Cons (3, Vide)))
let rec appartient e l = match l with (* teste si e appartient à l *)
| Vide -> false
| Cons(x, q) -> x = e || appartient e q
val appartient : 'a -> 'a list -> bool = <fun>
Exercice
- Écrire un type
number
qui représente des nombres soient entiers (int
), soit flottants (float
). - Écrire des opérations (addition...) sur ce type. L'addition d'un flottant avec un entier donnera un flottant.
- Écrire une fonction pour sommer les éléments d'une liste de
number
.
Solution
(* 1 *)
type number = F of float | I of int;;
(* 2 *)
let (+@) n1 n2 = match n1, n2 with
| F(f1), F(f2) -> F(f1 +. f2)
| I(n1), I(n2) -> I(n1 + n2)
| I(n1), F(n2) -> F(float_of_int n1 +. n2)
| F(n1), I(n2) -> F(n1 +. float_of_int n2);;
(* 3 *)
let rec somme = function
| [] -> I(0)
| e::q -> e +@ somme q in
somme [F 3.14; I 2; I 98; F 1.745]
type number = F of float | I of int
val ( +@ ) : number -> number -> number = <fun>
- : number = F 104.885
Type option¶
Le type option sert à stocker une valeur optionnelle, éventuellement None :
None
- : 'a option = None
Some 1
- : int option = Some 1
Le type option est définit comme ceci en OCaml :
type 'a option = None | Some of 'a
type 'a option = None | Some of 'a
Il est utile lorsqu'une fonction peut renvoyer aucune valeur.
Exercice
Écrire une fonction indice : 'a array -> 'a -> int option
telle que indice t e
renvoie Some i
tel que t[i] = e
, si un tel i
existe, et None
sinon.
Solution
let indice t e =
let res = ref None in
for i = 0 to Array.length t - 1 do
if t.(i) = e then res := Some i
done;
!res;;
indice [|0; 3; 2; 4; 5|] 4;;
indice [|0; 3; 2; 4; 5|] 1;;
val indice : 'a array -> 'a -> int option = <fun>
Pour savoir si une option est un None
ou Some
, on utilise un match
.
Type enregistrement (record)¶
Alors qu'un type somme fait une disjonction (un "ou") de plusieurs types, un type enregistrement (record) permet d'avoir plusieurs types simultanément (un "et" de plusieurs types).
type fraction = {num: int; den: int};; (* fraction composée d'un numérateur ET dénominateur *)
let x = {num=3; den=4};; (* x représente la fraction 3/4 *)
x.den (* obtient la valeur du champ den de x *)
type fraction = { num : int; den : int; }
val x : fraction = {num = 3; den = 4}
- : int = 4
fraction
est un type très proche de int*int
. La différence principale est que les composantes sont nommées dans un enregistrement mais pas dans un tuple.
Nous pouvons multiplier deux fractions :
let mult x1 x2 =
{num=x1.num*x2.num; den=x1.den*x2.den};;
mult x x
val mult : fraction -> fraction -> fraction = <fun>
- : fraction = {num = 9; den = 16}
Exercice
- Écrire une fonction pour additionner deux fractions.
- Écrire une fonction pour simplifier une fraction.
Solution
let add x1 x2 = { (* a/b + c/d = (a*d + b*c)/(b*d) *)
num = (x1.num*x2.den + x1.den*x2.num);
den = x1.den*x2.den
};;
let y = add x x;;
let simplify x =
let rec pgcd a b =
if b = 0 then a
else pgcd b (a mod b) in
let d = pgcd x.num x.den in
{num = x.num/d; den = x.den/d} in
simplify y;;
val add : fraction -> fraction -> fraction = <fun>
val y : fraction = {num = 24; den = 16}
- : fraction = {num = 3; den = 2}
L'intérêt d'utiliser des fractions plutôt que des float est de faire des calculs exacts et non pas approchés.
Exercice
- Définir deux types enregistrements pour représenter un nombre complexe sous forme algébrique et sous forme polaire.
- Écrire une fonction pour convertir un nombre complexe de polaire à algébrique.
Solution
type complexe = {re: float; im: float};;
type polaire = {r: float; theta: float};;
let polaire_to_algebrique z =
{re = z.r *. cos z.theta; im = z.r *. sin z.theta};;
polaire_to_algebrique {r = 1.; theta = 3.14/.2.}
type complexe = { re : float; im : float; }
type polaire = { r : float; theta : float; }
val polaire_to_algebrique : polaire -> complexe = <fun>
- : complexe = {re = 0.000796326710733263345; im = 0.99999968293183461}
Mot-clé mutable¶
Par défaut, un type enregistrement n'est pas mutable (on ne peut pas modifier ses éléments).
Cependant, il est possible d'ajouter un mot-clé mutable sur un attribut. On utilise alors <-
pour modifier l'attribut.
Par exemple, on pourrait redéfinir une référence comme un type enregistrement avec un seul champ mutable :
type 'a ref = {mutable v: 'a}
type 'a ref = { mutable v : 'a; }
let a = {v = 2} (* équivalent de let a = ref 2 *)
val a : int ref = {v = 2}
a.v (* équivalent de !a *)
- : int = 2
a.v <- 5 (* équivalent de a := 5 *)
- : unit = ()