Author: Christian.Queinnec@lip6.fr

## Bibliothèque standard

La bibliothèque standard regroupe un ensemble de modules stables. Ceux-ci sont indépendants des systèmes d'exploitation. Il y a actuellement 29 modules dans la bibliothèque standard contenant 400 fonctions, 30 types dont la moitié sont abstraits, 8 exceptions, 10 sous-modules et 3 modules paramétrés. Il est clair que nous n'allons pas détailler l'ensemble des déclarations de tous ces modules. En effet le manuel de référence [LRVD99] le fait déjà très bien. Seuls les modules présentant un nouveau concept ou une réelle difficulté de manipulation seront détaillés.

La bibliothèque standard peut être découpée en quatre grandes parties :

- structures linéaires de données (15 modules), dont certains sont déjà apparus dans la première partie ;
- entrées-sorties (4 modules), pour le formatage des sorties, la persistance et la création de clés pour la cryptographie ;
- analyses lexicale et syntaxique (4 modules). Leur description est reportée au chapitre 11 (page ??) ;
- interface avec le système qui permet de communiquer et d'analyser les paramètres passés à une commande, de naviguer dans les catalogues et d'accéder aux fichiers.

À ces quatre ensembles, on ajoute un cinquième contenant quelques utilitaires de manipulation ou de création de données comme des fonctions de traitement de caractères ou un générateur de nombres pseudo-aléatoires, etc.

### Utilitaires

Les modules que nous avons baptisés << utilitaires >> concernent :

- les caractères : module `Char` qui contient essentiellement des conversions ;
- copie d'objets : `Oo` qui sera présenté au chapitre 15 (page ??), sur la programmation objet ;
- évaluation retardée : `Lazy` présenté en première partie, page ?? ;
- générateur de nombres aléatoires : `Random` que nous décrivons ci-dessous.

#### Génération de nombres aléatoires

Le module `Random` est un générateur de nombres pseudo-aléatoires. Il implante une fonction de génération de nombres aléatoires à partir d'un nombre ou d'une suite de nombres appelé racine. Pour que cette fonction ne retourne pas toujours la même suite de nombres, le programmeur doit lui passer une racine différente à chaque initialisation. À partir de cette racine la fonction engendre une suite de nombres non prévisible. Néanmoins une initialisation avec la même racine créera la même suite. Il faut donc trouver une ressource extérieure au programme, comme la date du jour en millisecondes, ou le temps passé depuis le début du programme, pour initialiser correctement le générateur.
Voici les fonctions du module :

- initialisation : `init` de type int -> unit et `full_init` de type int array -> unit qui initialisent le générateur. La deuxième fonction prend un tableau de racines.
- demande d'un nombre : `bits` de type unit -> int retourne un entier positif, `int` de type int -> int retourne un entier positif compris entre 0 et une borne passée en tant que paramètre, et `float` qui retourne un flottant en lui passant une borne.

### Structures de données linéaires

Les modules sur les structures de données linéaires sont :

- modules simples : `Array`, `String`, `List`, `Sort`, `Stack`, `Queue`, `Buffer`, `Hashtbl` (qui est aussi paramétré) et `Weak` ;
- modules paramétrés : `Hashtbl` (de paramètre `HashedType`), `Map` et `Set` (de paramètre `OrderedType`).

Les modules paramétrés sont construits à partir d'autres modules accroissant ainsi leur généricité. La construction de modules paramétrés sera présentée au chapitre 14, page ??.

#### Structures linéaires de données simples

Le nom du module décrit le type de structures de données manipulées par le module. Si le type est abstrait, c'est-à-dire si sa représentation est masquée, la convention actuelle est de le nommer t à l'intérieur du module. Ces modules implantent les structures suivantes :

- module `Array` : vecteurs
- module `List` : listes
- module `String` : chaînes de caractères
- module `Hashtbl` : tables de hachage (type abstrait)
- module `Buffer` : chaînes de caractères extensibles (type abstrait)
- module `Stack` : piles (type abstrait)
- module `Queue` : files d'attente ou FIFO (type abstrait)
- module `Weak` : vecteur de pointeurs faibles (type abstrait)

Signalons un dernier module manipulant des structures linéaires :

- module `Sort` : tri sur les listes et les vecteurs, fusion de listes

##### Famille de fonctions communes

Tous ces modules, à l'exception du module `Sort`, définissent une structure de données, les fonctions de création et d'accès aux éléments de cette structures ainsi que des fonctions de manipulation incluant des conversions vers d'autres types. Seul le module `List` est sans modification physique. Nous ne donnerons pas la description complète de toutes ces fonctions. Néanmoins, nous allons nous intéresser aux familles de fonctions que l'on rencontre dans ces modules, puis nous détaillerons les modules `List` et `Array` qui sont les structures les plus communes en programmation fonctionnelle et impérative.
On retrouve peu ou prou les fonctionnalités suivantes dans tous les modules :

- une fonction `length` qui prend une valeur du type et calcule un entier correspondant à sa longueur ;
- une fonction `clear` remettant la structure linéaire à vide, si elle est modifiable ;
- une fonction d'ajout d'un élément, `add` en général, mais pouvant être dénommée différemment selon les habitudes (comme par exemple `push` pour les piles) ;
- une fonction d'accès au i-ème élément, souvent nommée `get` ;
- une fonction de retrait d'un élément (souvent le premier) `remove` ou `take`.

De même certaines conventions sur les fonctions de parcours et de traitement se retrouvent dans un certain nombre de modules :

- `map` : qui applique une fonction sur tous les éléments de la structure et retourne une nouvelle structure contenant les résultats de ces appels ;
- `iter` : proche de `map` mais ne tient pas compte des résultats et retourne ().

Pour les structures dont les éléments sont indicés. on a :

- `fill` : qui remplace (modifie en place) une partie de la structure par une valeur ;
- `blit` : qui copie une partie d'une structure dans une autre structure du même type ;
- `sub` : qui copie une partie d'une structure et en crée une nouvelle.

#### Modules `List` et `Array`

Nous décrivons les fonctions de ces deux bibliothèques en mettant l'accent sur les similitudes et les particularités de chacune d'elles. Pour les fonctions communes aux deux modules, t désigne le type 'a list ou 'a array. Lorsqu'une fonction est propre à un module, nous utiliserons la notation pointée.

##### Fonctionnalités communes ou analogues

La première d'entre elles est le calcul de la longueur.

`length` | : | 'a t -> int

Deux fonctions permettent de concaténer deux structures ou toutes les structures d'une liste.

`append` | : | 'a t -> 'a t -> 'a t
`concat` | : | 'a t list -> 'a t

Chacun des deux modules possède une fonction d'accès à un élément désigné par sa position dans la structure.

`List.nth` | : | 'a list -> int -> 'a
`Array.get` | : | 'a array -> int -> 'a

La fonction d'accès à un élément d'indice `i` d'un vecteur `t`, dont l'usage est fréquent, possède un raccourci syntaxique : t.(i).
Deux fonctions permettent d'appliquer un traitement (une fonction) à tous les éléments d'une structure.

`iter` | : | ('a -> unit) -> 'a t -> unit
`map` | : | ('a -> 'b) -> 'a t -> 'b t

On peut utiliser `iter` pour afficher le contenu d'une liste ou d'un vecteur.

    # let print_content iter print_item xs =
       iter (fun x -> print_string"("; print_item x; print_string")") xs;
       print_newline() ;;
    val print_content : (('a -> unit) -> 'b -> 'c) -> ('a -> 'd) -> 'b -> unit =
      <fun>
    # print_content List.iter print_int [1;2;3;4;5] ;;
    (1)(2)(3)(4)(5)
    - : unit = ()
    # print_content Array.iter print_int [|1;2;3;4;5|] ;;
    (1)(2)(3)(4)(5)
    - : unit = ()

La fonction `map` construit une nouvelle structure contenant le résultat de l'application. On peut le voir par exemple sur les vecteurs dont le contenu est modifiable :

    # let a = [|1;2;3;4|] ;;
    val a : int array = [|1; 2; 3; 4|]
    # let b = Array.map succ a ;;
    val b : int array = [|2; 3; 4; 5|]
    # a, b;;
    - : int array * int array = [|1; 2; 3; 4|], [|2; 3; 4; 5|]

Deux itérateurs permettent de composer l'application partielle d'une fonction à chacun des éléments d'une structure.

`fold_left` | : | ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a
`fold_right` | : | ('a -> 'b -> 'b) -> 'a t -> 'b -> 'b

Il faut fournir à ces itérateurs un cas de base qui donne la valeur par défaut lorsque la structure est vide.

fold_left`` f`` r`` `[`v1;`` v2;`` `...`;`` `` vn`]` | = | f`` `...``` `(``` f`` `` `(`f`` r`` v1`)``` v2`` `)``` `...``` vn
fold_right`` f`` `[`v1;`` v2;`` `...`;`` vn`]``` r`` | = | f`` v1`` `(``` f`` v2`` `` `` `...``` `(`f`` vn`` r`)``` `...``` `)```

Ces fonctions permettent de transformer facilement des opérations binaires en opérations n-aires. Lorsque l'opération est commutative et associative, l'itération à gauche ou à droite est indifférente :

    # List.fold_left (+) 0 [1;2;3;4] ;;
    - : int = 10
    # List.fold_right (+) [1;2;3;4] 0 ;;
    - : int = 10
    # List.fold_left List.append [0] [[1];[2];[3];[4]] ;;
    - : int list = [0; 1; 2; 3; 4]
    # List.fold_right List.append [[1];[2];[3];[4]] [0] ;;
    - : int list = [1; 2; 3; 4; 0]

Remarquons que la liste vide est élément neutre à gauche et à droite pour la concaténation binaire. On retrouve donc, pour ce cas particulier, l'équivalence des deux expressions :

    # List.fold_left List.append [] [[1];[2];[3];[4]] ;;
    - : int list = [1; 2; 3; 4]
    # List.fold_right List.append [[1];[2];[3];[4]] [] ;;
    - : int list = [1; 2; 3; 4]

On a, en fait, retrouvé la fonction `List.concat`.

##### Manipulations propres aux listes

Il est d'usage d'avoir sur les listes les fonctions suivantes que fournit le module `List` :

`List.hd` | : | 'a list -> 'a
|  | premier élément de la liste
`List.tl` | : | 'a list -> 'a
|  | liste privée de son premier élément
`List.rev` | : | 'a list -> 'a list
|  | retournement d'une liste
`List.mem` | : | 'a -> 'a list -> bool
|  | test d'appartenance
`List.flatten` | : | 'a list list -> 'a list
|  | aplatissement d'une liste de listes
`List.rev_append` | : | 'a list -> 'a list -> 'a list
|  | est égale à append (rev l1) l2

Les deux premières fonctions sont partielles. Elles ne sont pas définies sur la liste vide et déclenchent une exception `Failure`. Il existe une variante de `mem` : `memq` qui utilise l'égalité physique.

    # let c = (1,2) ;;
    val c : int * int = 1, 2
    # let l = [c] ;;
    val l : (int * int) list = [1, 2]
    # List.memq (1,2) l ;;
    - : bool = false
    # List.memq c l ;;
    - : bool = true

Le module `List` fournit deux itérateurs particuliers généralisant la conjonction et la disjonction booléennes : `List.for_all` et `List.exists` que l'on définit par itération :

    # let for_all f xs = List.fold_right (fun x -> fun b -> (f x) & b) xs true ;;
    val for_all : ('a -> bool) -> 'a list -> bool = <fun>
    # let exists f xs = List.fold_right (fun x -> fun b -> (f x) or b) xs false ;;
    val exists : ('a -> bool) -> 'a list -> bool = <fun>

Il existe des variantes de tous les itérateurs du module `List` prenant en argument deux listes et les parcourant en parallèle (`iter2`, `map2`, etc.). Si elles ne sont pas de même longueur, l'exception `Invalid_argument` est déclenchée.
La recherche d'éléments d'une liste selon un critère donné sous forme de fonction booléenne peut se faire en utilisant les fonctions suivantes :

`List.find` | : | ('a -> bool) -> 'a list -> 'a
`List.find_all` | : | ('a -> bool) -> 'a list -> 'a list

La fonction `find_all` a un alias : `filter`.
Une variante de la fonction générale de recherche est la fonction de partitionnement d'une liste :

`List.partition` | : | ('a -> bool) -> 'a list -> 'a list * 'a list

Comme utilitaires souvent nécessaires, on trouve dans le module `List` deux fonctions permettant de diviser ou de créer des listes de couples :

`List.split` | : | ('a * 'b) list -> 'a list * 'b list
`List.combine` | : | 'a list -> 'b list -> ('a * 'b) list

Enfin, une structure combinant listes et couples est souvent utilisée : les listes d'associations. Elles servent à stocker des valeurs associées à des clés. Ce sont des listes de couples dont la première composante joue le rôle de clé et la seconde, celui d'information associée. On a pour traiter de telles données :

`List.assoc` | : | 'a -> ('a * 'b) list -> 'b
|  | extrait l'information associée à une clé
`List.mem_assoc` | : | 'a -> ('a * 'b) list -> bool
|  | teste l'existence d'une clé
`List.remove_assoc` | : | 'a -> ('a * 'b) list -> ('a * 'b) list
|  | suppression d'un élément correspondant à une clé

Ces fonctions ont chacune une variante utilisant l'égalité physique au lieu de l'égalité structurelle: `List.assq`, `List.mem_assq` et `List.remove_assq`.

##### Manipulations propres aux vecteurs

Les vecteurs que l'on utilise souvent en programmation impérative sont des structures physiquement modifiables. Le module `Array` fournit une fonction de modification de la valeur d'un élément :

`Array.set` | : | 'a array -> int -> 'a -> unit

Comme `get`, la fonction `set` possède un raccourci syntaxique : t.(i) <- a.
Il existe trois fonctions d'allocation de vecteurs :

`Array.create` | : | int -> 'a -> 'a array
|  | crée un vecteur d'une taille donnée dont tous les éléments sont initialisés avec une même valeur
`Array.make` | : | int -> 'a -> 'a array
|  | alias de `create`
`Array.init` | : | int -> (int -> 'a) -> 'a array
|  | crée un vecteur d'une taille donnée dont chaque élément est initialisé avec le résultat de l'application d'une fonction à l'indice de l'élément initialisé

Comme leur usage est courant, le module `Array` fournit deux fonctions de création de matrices (vecteurs de vecteurs) :

`Array.create_matrix` | : | int -> int -> 'a -> 'a array array
`Array.make_matix` | : | int -> int -> 'a -> 'a array array

La fonction `set` se généralise en une fonction modifiant les valeurs d'un intervalle décrit par un indice de départ et une longueur :

`Array.fill` | : | 'a array -> int -> int -> 'a -> unit

On peut copier l'intégralité d'un vecteur ou extraire un sous vecteur (décrit par un indice de début et une longueur) pour obtenir une nouvelle structure :

`Array.copy` | : | 'a array -> 'a array
`Array.sub` | : | 'a array -> int -> int -> 'a array

La copie ou l'extraction peuvent aussi se faire vers un autre vecteur :

`Array.blit` | : | 'a array -> int -> 'a array -> int -> int -> unit

Le premier argument entier est l'indice dans le premier vecteur, le deuxième, l'indice dans le second vecteur et le troisième argument entier, le nombre de valeurs copiées. Les trois fonctions `blit`, `sub` et `fill` déclenchent l'exception `Invalid_argument`.
L'usage privilégié des indices dans les fonctions de manipulation de vecteurs a conduit à la définition de deux itérateurs particuliers :

`Array.iteri` | : | (int -> 'a -> unit) -> 'a array -> unit
`Array.mapi` | : | (int -> 'a -> 'b) -> 'a array -> 'b array

Elles appliquent une fonction dont le premier argument est l'indice de la case traitée.

    #   let f i a = (string_of_int i) ^ ":" ^ (string_of_int a) in
       Array.mapi f  [| 4; 3; 2; 1; 0 |] ;;
    - : string array = [|"0:4"; "1:3"; "2:2"; "3:1"; "4:0"|]

Le module `Array` ne fournit pas de fonction permettant de modifier en place le contenu des cases d'un vecteur avec le résultat d'un calcul sur leur contenu, mais on peut facilement l'obtenir avec `iteri` :

    # let iter_and_set f t =
      Array.iteri (fun i -> fun x -> t.(i) <- f x) t ;;
    val iter_and_set : ('a -> 'a) -> 'a array -> unit = <fun>
    # let v = [|0;1;2;3;4|] ;;
    val v : int array = [|0; 1; 2; 3; 4|]
    # iter_and_set succ v ;;
    - : unit = ()
    # v ;;
    - : int array = [|1; 2; 3; 4; 5|]

Enfin, le module `Array` fournit deux fonctions de conversion avec les listes :

`Array.of_list` | : | 'a list -> 'a array
`Array.to_list` | : | 'a array -> 'a list

### Entrées-sorties

La bibliothèque standard comporte les quatre modules d'entrées-sorties :

- module `Printf` : pour le formatage en sortie;
- `Format` : pour un formatage en boîte gérant automatiquement les sauts de ligne;
- module `Marshal` : implantant un mécanisme de valeurs persistantes ;
- module `Digest` : pour créer des clés uniques.

La description du module `Marshal` sera donnée plus loin dans ce chapitre lorsque nous aborderons le traitement des données persistantes (voir page ??).

#### Module `Printf`

Le module `Printf` permet de formater l'affichage à la manière de la fonction `printf` de la bibliothèque du langage C. Le format d'affichage est représenté sous forme d'une chaîne de caractères, qui sera décodée selon les conventions du `printf` de C, c'est-à-dire en spécialisant le caractère `%`. C'est ce caractère suivi d'une lettre qui indiquera le type de l'argument à mettre à cet emplacement. Le format suivant "(x=%d, y=%d)" indique qu'il faudra placer deux entiers à la place des %d dans la chaîne de sortie.

##### Spécifications des formats

Un format définit des paramètres pour une chaîne d'affichage. Ceux-ci, de type de base : int, float, char et string, seront convertis en chaînes et remplaceront leur occurrence dans la chaîne d'affichage. Les valeurs 77 et 43 fournie au format "(x=%d, y=%d)" engendreront la chaîne d'affichage complète `"(x=77, y=43)"`. Les principales lettres indiquant le type de la conversion à effectuer sont données figure 8.1.

> Type | Lettre | Résultat
> entier | `d` ou `i` | décimal signé
> | `u` | décimal non signé
> | `x` | hexadécimal non signé en minuscule
> | `X` | idem en majuscule
> caractère | `c` | caractère
> chaîne | `s` | chaîne
> flottant | `f` | décimal
> | `e` ou `E` | en notation avec exposant
> | `g` ou `G` | idem
> booléen | `b` | `true` ou `false`
> spécial | `a` ou `t` | paramètre fonctionnel
> |  | de type (out_channel -> 'a -> unit) -> 'a -> unit
> |  | ou out_channel -> unit

> Figure 8.1 : Convention de conversion

Le format permet aussi d'assurer le cadrage d'une conversion, ce qui permet l'alignement des valeurs à afficher. On peut principalement indiquer la taille en caractères de la conversion. Pour cela on intercalera entre le caractère % et le type de conversion un nombre entier comme dans %10d qui indique une conversion essayant de se cadrer à droite sur dix caractères. Si la taille de la conversion dépasse cette limite, il ne sera pas tenu compte de cette limite. Un nombre négatif effectuera un cadrage à gauche. Pour les conversions de nombres flottants, il est utile de pouvoir spécifier la précision d'affichage. On intercalera alors un point suivi d'un nombre pour indiquer le nombre de caractères après la virgule comme dans %.5f qui indique cinq caractères à droite du point décimal.
Il existe deux lettres de format particulières : `a` et `t` qui indiquent un argument fonctionnel. Typiquement, une fonction d'affichage définie par l'utilisateur. C'est une spécificité d'Objective CAML.

##### Fonctions du module

Les types des cinq fonctions de ce module sont donnés figure 8.2.

> `fprintf` | : | out_channel -> ('a, out_channel, unit) format -> 'a
> `printf` | : | ('a, out_channel, unit) format -> 'a
> `eprintf` | : | ('a, out_channel, unit) format -> 'a
> `sprintf` | : | ('a, unit, string) format -> 'a
> `bprintf` | : | Buffer.t -> ('a, Buffer.t, string) format -> 'a

> Figure 8.2 : Fonctions de formatage de `Printf`

La fonction `fprintf` prend un canal, un format et des arguments de type décrit dans le format. Les fonctions `printf` et `eprintf` sont des versions spécialisées sur la sortie standard et la sortie erreur. Enfin `sprintf` et `bprintf` n'affichent pas le résultat de la conversion, mais retournent la chaîne correspondante.
Voici quelques exemples simples d'utilisation des formats.

    # Printf.printf "(x=%d, y=%d)" 34 78 ;;
    (x=34, y=78)- : unit = ()
    # Printf.printf "nom = %s, age = %d" "mapom" 18 ;;
    nom = mapom, age = 18- : unit = ()
    # let s = Printf.sprintf "%10.5f\n%10.5f\n" (-.12.24) (2.30000008) ;;
    val s : string = " -12.24000\n   2.30000\n"
    # print_string s ;;
    -12.24000
       2.30000
    - : unit = ()

L'exemple suivant construit une fonction d'affichage d'une matrice de flottants sous un format donné.

    # let print_mat m =
       Printf.printf "\n" ;
       for i=0 to (Array.length m)-1 do
         for j=0 to (Array.length m.(0))-1 do
           Printf.printf "%10.3f" m.(i).(j)
         done ;
         Printf.printf "\n"
       done ;;
    val print_mat : float array array -> unit = <fun>
    # print_mat (Array.create 4  [| 1.2; -.44.22; 35.2 |]) ;;

         1.200   -44.220    35.200
         1.200   -44.220    35.200
         1.200   -44.220    35.200
         1.200   -44.220    35.200
    - : unit = ()

##### Remarque sur le type format

La description d'un format adopte la syntaxe des chaînes de caractères, mais ce n'est pas une valeur de type string. Le décodage d'un format, selon les conventions précédentes, construit une valeur de type format où le paramètre 'a est instancié soit à unit si le format ne mentionne pas de paramètre, soit par un type fonctionnel correspondant à une fonction pouvant recevoir autant d'arguments que mentionnés et retournant la valeur de type unit.
On peut illustrer ce traitement en appliquant partiellement la fonction `printf` à un format :

    # let p3 =
       Printf.printf "début\n%d est val1\n%s est val2\n%f est val3\n" ;;
    début
    val p3 : int -> string -> float -> unit = <fun>

On obtient ainsi une fonction qui attend trois arguments. Remarquez que le mot `début` a déjà été affiché. Un autre format aurait donné un autre type de fonction :

    # let p2 =
       Printf.printf "début\n%f est val1\n%s est val2\n";;
    début
    val p2 : float -> string -> unit = <fun>

En fournissant petit à petit ses arguments à `p3`, on obtient les affichages au fur et à mesure :

    # let p31 = p3 45 ;;
    45 est val1
    val p31 : string -> float -> unit = <fun>
    # let p32 = p31 "hello" ;;
    hello est val2
    val p32 : float -> unit = <fun>
    # let p33 = p32 3.14 ;;
    3.140000 est val3
    val p33 : unit = ()
    # p33 ;;
    - : unit = ()

La dernière valeur obtenue n'affiche rien : c'est la valeur () du type unit.
On ne peut pas construire un format en utilisant des valeurs de type string :

    # let f d =
      Printf.printf (d^d);;
    Characters 27-30:
    This expression has type string but is here used with type
      ('a, out_channel, unit) format

Le compilateur ne peut connaître la valeur de la chaîne passée en argument. Il ne peut donc pas connaître le type qui sert à instancier le paramètre 'a du type `format`.
D'autre part, les chaînes de caractères sont des valeurs physiquement modifiables, il serait alors possible de modifier, par exemple, la partie %d par une autre lettre, modifiant ainsi dynamiquement un format d'affichage. C'est en contradiction avec la génération statique de la fonction de conversion.

#### Module `Digest`

Une fonction de hachage convertit une chaîne de caractères de taille quelconque en une chaîne de caractères de taille fixe, le plus souvent plus petite. Ces fonctions de hachage retournent une empreinte (digest) de leur entrée.
De telles fonctions sont utilisées pour la construction de tables de hachage, comme dans le module `Hashtbl`, permettant de tester rapidement si un élément appartient à une telle table grâce à un accès direct à l'empreinte. Par exemple la fonction `f_mod_n`, qui effectue la somme des codes ASCII des caractères d'une chaîne modulo n, est une fonction de hachage. Si on crée un tableau de dimension n pour ranger ces chaînes, de part l'empreinte on obtient un accès direct. Néanmoins deux chaînes peuvent retourner la même empreinte. Dans ces cas de collisions, on ajoute à la table de hachage une extension pour stocker ces éléments. S'il y a trop de collisions, alors l'accès à la table de hachage est peu efficace. Si l'empreinte a une taille de n bits, alors la probabilité de collision entre deux chaînes différentes est de 1/2n.
Une fonction de hachage à sens unique possède une probabilité très faible de collision. Il est ainsi difficile, étant donnée une empreinte, de construire une chaîne possédant cette empreinte. La fonction précédente `f_mod_n` n'en est, à l'évidence, pas une. Les fonctions de hachage à sens unique permettent l'authentification d'une chaîne, que cela soit pour un texte envoyé sur Internet, un fichier, etc.
Le module `Digest` utilise l'algorithme MD5, pour Message Digest 5. Il retourne une empreinte sur 128 bits. Bien que l'algorithme soit public, il est impossible (à ce jour) d'effectuer une reconstruction à partir d'une empreinte. Ce module définit le type Digest.t comme une abréviation du type string. La figure 8.3 détaille les principales fonctions de ce module.

> `string` | : | string -> t
> |  | retourne l'empreinte d'une chaîne
> `file` | : | string -> t
> |  | retourne l'empreinte d'un fichier

> Figure 8.3 : Fonctions du module `Digest`

On utilise la fonction `string` dans l'exemple suivant sur une petite chaîne et sur une grande construite à partir de la première. L'empreinte est toujours de taille fixe.

    # let s = "Le petit chat est mort...";;
    val s : string = "Le petit chat est mort..."
    # Digest.string s;;
    - : Digest.t = "[\138\236]5\015\156'\243\232>\176IdF\022"

    # let r = ref s in
       for i=1 to 100 do r:= s^ !r done;
       Digest.string !r;;
    - : Digest.t = "\148\022\201?j\141\159\143\168\241;\004\158\139\196\147"

La création d'une empreinte sur des programmes permet de garantir le contenu de celui-ci évitant ainsi l'utilisation d'une mauvaise version. Par exemple, lors du chargement dynamique de code (voir page ??), une empreinte est utilisée pour sélectionner le fichier de code-octet à charger.

    # Digest.file "basic.ml" ;;
    - : Digest.t = "\179\026\191\137\157Ly|^w7\183\164:\167q"

### Persistance

La persistance est la conservation d'une valeur en dehors de l'exécution courante d'un programme. C'est le cas quand on écrit une valeur dans un fichier. Cette valeur est alors accessible à tout programme ayant accès au fichier. L'écriture et la lecture d'une valeur persistante réclament la définition d'un format de représentation ou codage des données. En effet, il faut savoir passer d'une structure complexe stockée en mémoire, telle un arbre, à une structure linéaire, une suite d'octets, stockée sur fichier. C'est pourquoi le codage des valeurs persistantes s'appelle linéarisation1.

#### Réalisation et difficultés de la linéarisation

La mise en oeuvre d'un mécanisme de linéarisation des données demande des choix et présente des difficultés que nous décrivons ci-dessous.

- lecture-écriture de données. Comme la mémoire peut toujours être vue comme un vecteur de mots, une valeur peut toujours correspondre à la mémoire qu'elle occupe, quitte à ne conserver que la partie utile en compactant alors la valeur.
- partage ou copie. L'aplatissement d'une donnée doit-il conserver le partage ? Typiquement un arbre binaire qui possède deux fils identiques (au sens de l'égalité physique) peut indiquer, pour le deuxième fils, qu'il a déjà sauvegardé le premier. Cette caractéristique influence la taille de la valeur sauvegardée et le temps mis pour le faire. D'autre part, en présence de valeurs physiquement modifiables, cela peut changer le comportement de cette valeur après une récupération selon que le partage a été conservé ou non.
- structures circulaires. Dans le cas d'une valeur circulaire, la linéarisation sans partage risque de boucler. Il sera nécessaire de conserver ce partage.
- valeurs fonctionnelles. Les valeurs fonctionnelles, ou fermetures, se composent d'une partie environnement et d'une partie code. La partie code correspond au point d'entrée (adresse) du code à exécuter. Que faut-il faire alors du code? Il est possible de stocker uniquement cette adresse, mais alors seul le même programme trouvera un sens correct à cette adresse. Il est aussi possible de sauver la suite d'instructions machine de cette fonction, mais il sera nécessaire d'avoir un mécanisme de chargement dynamique de code.
- garantie du type à la relecture. C'est la principale difficulté de ce mécanisme. Le typage statique garantit que les valeurs typées n'engendreront pas d'erreur de type à l'exécution. Mais cela n'est vrai que pour les valeurs appartenant au programme en cours d'exécution. Quel type peut-on donner à une valeur extérieure au programme, qui n'a donc pas été vue par le vérificateur de types? Pour seulement vérifier que la valeur relue possède le type monomorphe engendré par le compilateur, il faudrait qu'il y ait une transmission de ce type au moment de la sauvegarde, puis vérification au chargement. Avec de surcroît un mécanisme de gestion de versions des types, pour rester sûr, même en cas de redéclaration d'un type dans le programme.

#### Module `Marshal`

Le mécanisme de linéarisation du module `Marshal` permet, au choix, de retenir ou non le partage des valeurs traitées. Il permet également de traiter les fermetures, mais, dans ce cas, seul le pointeur de code est conservé.
Ce module comporte principalement les fonctions de linéarisation vers un canal ou une chaîne et les fonctions de récupération à partir d'un canal ou d'une chaîne. Les fonctions de linéarisation sont paramétrables. Le type suivant déclare les deux options possibles :

    type external_flag =
      No_sharing
    | Closures;;

Le constructeur constant `No_sharing` indique de ne pas conserver le partage d'une valeur, par défaut il l'est. Le constructeur `Closures` permet de traiter les fermetures en conservant son pointeur de code. Son absence déclenchera une exception si l'on essaie de conserver une valeur fonctionnelle.

### Warning

Le constructeur `Closures` est inopérant en mode interactif. Il ne peut être utilisé qu'en mode ligne de commande.

Les fonctions d'écriture et de lecture de ce module sont regroupées à la figure 8.4.

> `to_channel` | : | out_channel -> 'a -> extern_flag list -> unit
> `to_string` | : | 'a -> extern_flag list -> string
> `to_buffer` | : | string -> int -> int -> 'a -> extern_flag list -> unit
> `from_channel` | : | in_channel -> 'a
> `from_string` | : | string -> int -> 'a

> Figure 8.4 : Fonctions du module `Marshal`

La fonction `to_channel` prend un canal de sortie, une valeur et une liste d'options et écrit la valeur passée sur le canal. La fonction `to_string` produit une chaîne correspondant à la valeur linéarisée, alors que `to_buffer` effectue le même travail en modifiant une partie d'une chaîne passée en argument. La fonction `from_channel` lit sur un canal une valeur linéarisée et la retourne. La variante `from_string` prend en entrée une chaîne et la position du premier caractère à lire dans la chaîne. Plusieurs valeurs linéarisées peuvent être stockées dans le même fichier ou dans la même chaîne. Dans le premier cas, elles pourront être lues séquentiellement. Dans le deuxième cas, il faudra préciser le bon décalage par rapport au début de la chaîne pour décoder la valeur désirée.

    # let s = Marshal.to_string [1;2;3;4] [] in String.sub s 0 10;;
    - : string = "\132\149\166\190\000\000\000\t\000\000"

### Warning

L'utilisation de ce module fait perdre la sûreté du typage statique (voir infra, page ??).

La relecture d'un objet persistant crée une valeur de type indéterminé :

    # let x = Marshal.from_string (Marshal.to_string [1; 2; 3; 4] []) 0;;
    val x : '_a = <poly>

Cette indétermination est marquée, en Objective CAML, par la variable de type faible '_a. Il est recommandé de spécifier le type attendu :

    # let l =
       let s = (Marshal.to_string [1; 2; 3; 4] []) in
         (Marshal.from_string s 0 : int list) ;;
    val l : int list = [1; 2; 3; 4]

Nous revenons page ?? sur ce point.

### Remarque

La fonction `output_value` de la bibliothèque préchargée correspond à l'appel de `to_channel` avec une liste d'options vide. La fonction `input_value` du module `Pervasives` appelle directement la fonction `from_channel`. Celles-ci ont été conservées pour la compatibilité des anciens programmes.

#### Exemple : sauvegarde d'écrans

On cherche à sauvegarder le bitmap, représenté comme matrice de couleurs, de toute la fenêtre graphique. La fonction `save_screen` récupère le bitmap, le convertit en tableau de couleurs et le sauve dans un fichier dont le nom a été passé en paramètre.

    # let save_screen name =
      let i = Graphics.get_image 0 0 (Graphics.size_x ())
                                     (Graphics.size_y ())  in
       let j = Graphics.dump_image i in
       let oc = open_out name in
         output_value oc j;
         close_out oc;;
    val save_screen : string -> unit = <fun>

La fonction `load_screen` effectue l'opération inverse. Elle ouvre le fichier dont le nom est passé en paramètre, récupère la valeur stockée à l'intérieur, convertit cette matrice couleurs en bitmap puis affiche celui-ci.

    # let load_screen name =
       let ic = open_in name in
       let dessin = ((input_value ic) : Graphics.color array array) in
         close_in ic;
         Graphics.close_graph();
         Graphics.open_graph (":0 "^(string_of_int(Array.length dessin.(0)))
                            ^"x"^(string_of_int(Array.length dessin)));
        let dessin2 = Graphics.make_image dessin in
          Graphics.draw_image dessin2 0 0; dessin2 ;;
    val load_screen : string -> Graphics.image = <fun>

### Warning

Les valeurs dont le type est abstrait ne peuvent pas être persistantes.

C'est pour cela que l'exemple précédent n'utilise pas le type Graphics.image qui est abstrait, mais bien le type color array array qui lui est concret. L'abstraction de types est présentée au chapitre 14.

#### Partage

La perte du partage d'une donnée peut lui faire perdre complètement son intérêt. Reprenons l'exemple du générateur de symboles de la page ??. Pour une raison quelconque, on désire sauver les valeurs fonctionnelles `new_s` et `reset_s`, pour, par la suite, repartir de la valeur de leur compteur commun. On écrit alors le programme suivant :

    # let reset_s,new_s =
       let c = ref 0 in
       ( function () -> c := 0 ) ,
       ( function s ->  c:=!c+1; s^(string_of_int !c) ) ;;

    # let save =
       Marshal.to_string (new_s,reset_s) [Marshal.Closures;Marshal.No_sharing] ;;

    # let (new_s1,reset_s1) =
      (Marshal.from_string save 0 : ((string -> string ) * (unit -> unit))) ;;

    # (* 1 *)
     Printf.printf "new_s : \%s\n" (new_s "X");
     Printf.printf "new_s : \%s\n" (new_s "X");
     (* 2 *)
     Printf.printf "new_s1 : \%s\n" (new_s1 "X");
     (* 3 *)
     reset_s1();
     Printf.printf "new_s1 (après reset_s1) : \%s\n" (new_s1 "X") ;;
    Characters 148-154:
    Unbound value new_s1

Les deux premiers affichages en `(* 1 *)` sont cohérents par rapport à la définition. L'affichage obtenu en `(* 2 *)` après relecture des fermetures semble également correct (après `X2` vient `X3`). Mais, en fait, le partage du compteur `c` entre les fonctions relues `new_s1` et `reset_s1` est perdu comme l'atteste l'affichage de `X4` bien que l'on ait entre temps remis le compteur à zéro. Chaque fermeture a une copie du compteur et l'appel à `reset_s1` ne remet pas à zéro le compteur de `new_s1`. Il n'aurait donc pas fallu utiliser l'option `No_sharing` lors de la linéarisation.
Il est en règle générale nécessaire de conserver le partage. Néanmoins dans certains cas où la vitesse d'exécution est importante, l'absence de partage effectue un parcours plus rapide à la sauvegarde. L'exemple suivant montre une fonction qui effectue une copie d'une matrice. Dans ce cas là aussi il peut être préférable de casser le partage :

    # let copie_mat_f (m : float array array) =
      let s = Marshal.to_string m [Marshal.No_sharing] in
        (Marshal.from_string s 0 : float array array);;
    val copie_mat_f : float array array -> float array array = <fun>

On peut aussi l'utiliser pour la création d'une matrice sans partage :

    # let create_mat_f n m v =
       let m = Array.create n (Array.create m v) in
         copie_mat_f m;;
    val create_mat_f : int -> int -> float -> float array array = <fun>
    # let a = create_mat_f 3 4 3.14;;
    val a : float array array =
      [|[|3.14; 3.14; 3.14; 3.14|]; [|3.14; 3.14; 3.14; 3.14|];
        [|3.14; 3.14; 3.14; 3.14|]|]
    # a.(1).(2) <- 6.28;;
    - : unit = ()
    # a;;
    - : float array array =
    [|[|3.14; 3.14; 3.14; 3.14|]; [|3.14; 3.14; 6.28; 3.14|];
      [|3.14; 3.14; 3.14; 3.14|]|]

Ce qui est un comportement plus habituel que celui de `Array.create` et ressemble à celui de `Array.create_matrix`.

#### Taille des valeurs

Il peut être utile de connaître la taille d'un persistant. Si le partage est conservé, cette taille reflète assez bien l'occupation mémoire d'une valeur. Bien que le codage optimise parfois la taille des valeurs immédiates2, l'information de taille de leur codage respectif permet de comparer différentes implantations d'une structure de données. D'autre part pour les programmes qui ne s'arrêtent jamais, logiciels embarqués ou même serveurs réseau, l'étude de la taille des données peut indiquer des fuites de mémoire. Le module `Marshal` a deux fonctions de calcul de la taille et une constante qui sont décrits à la figure 8.5.

> `header_size` | : | int
> `data_size` | : | string -> int -> int
> `total_size` | : | string -> int -> int

> Figure 8.5 : Fonctions de taille de `Marshal`

La taille totale d'un persistant est égale à la taille de ses données additionnée à la taille de l'en-tête.
Nous donnons ci-dessous un petit exemple d'utilisation du codage MD5 pour comparer deux représentations des arbres binaires :

    # let taille x = Marshal.data_size (Marshal.to_string x []) 0;;
    val taille : 'a -> int = <fun>
    # type 'a bintree1 = Empty1 | Node1 of 'a * 'a bintree1 * 'a bintree1 ;;
    type 'a bintree1 = | Empty1 | Node1 of 'a * 'a bintree1 * 'a bintree1
    # let t1 =
      Node1(2, Node1(1, Node1(0, Empty1, Empty1), Empty1),
               Node1(3, Empty1, Empty1)) ;;
    val t1 : int bintree1 =
      Node1
       (2, Node1 (1, Node1 (0, Empty1, Empty1), Empty1),
        Node1 (3, Empty1, Empty1))
    # type 'a bintree2 =
      Empty2 | Leaf2 of 'a | Node2 of 'a * 'a bintree2 * 'a bintree2 ;;
    type 'a bintree2 =
      | Empty2
      | Leaf2 of 'a
      | Node2 of 'a * 'a bintree2 * 'a bintree2
    # let t2 =
      Node2(2, Node2(1, Leaf2 0, Empty2), Leaf2 3) ;;
    val t2 : int bintree2 = Node2 (2, Node2 (1, Leaf2 0, Empty2), Leaf2 3)
    # let s1, s2 = taille t1, taille t2 ;;
    val s1 : int = 13
    val s2 : int = 9

Les valeurs données par la fonction `taille` reflètent bien l'intuition que l'on peut avoir de la taille de `t1` et `t2`.

#### Problème de typage

Le vrai problème avec les persistants est qu'il est possible de casser le système de types d'Objective CAML. Les fonctions de création retournent bien un type monomorphe (unit ou string). En revanche, les fonctions de récupération retournent un type polymorphe 'a. Du point de vue des types, on pourra faire n'importe quel usage d'une valeur persistante. Voici le pire d'entre eux (voir chapitre 2, page ??) : créer une fonction `copie_magique` de type 'a -> 'b.

    # let copie_magique a =
      let s = Marshal.to_string a [Marshal.Closures] in
        Marshal.from_string s 0;;
    val copie_magique : 'a -> 'b = <fun>

L'utilisation d'une telle fonction provoque un arrêt brutal de l'exécution.

    #  (copie_magique 3 : float) +. 3.1;;
    Segmentation fault

En mode interactif (sous Linux), on sort même de la boucle d'interaction avec un signal d'erreur système correspondant à une violation de mémoire.

### Interface avec le système

La bibliothèque standard comporte six modules d'interface avec le système :

- module `Sys` : pour la communication entre le système d'exploitation et le programme ;
- module `Arg` : pour analyser les paramètres passés au programme sur la ligne de commande ;
- module `Filename` : pour la navigation dans les répertoires (indépendamment du système) ;
- module `Printexc` : pour l'interception et l'affichage des exceptions ;
- module `Gc` : pour le contrôle du mécanisme de récupération automatique de mémoire qui est décrit au chapitre 9 ;
- module `Callback` : pour l'appel de fonctions Objective CAML à partir de C qui est décrit au chapitre 12.

Les quatre premiers modules sont décrits ci-dessous.

#### Module `Sys`

Ce module apporte de bien utiles fonctions de communication avec le système d'exploitation, ainsi que le traitement des signaux reçus par un programme. Les valeurs de la figure 8.6 donnent des informations sur le système.

> `OS_type` | : | string
> |  | type du système
> `interactive` | : | bool ref
> |  | vrai si exécution au toplevel
> `word_size` | : | string
> |  | taille d'un mot (32 ou 64 bits)
> `max_string_length` | : | int
> |  | taille maximale d'une chaîne
> `max_array_length` | : | int
> |  | taille maximale d'un vecteur
> `time` | : | unit -> float
> |  | donne le temps en secondes depuis le début du programme

> Figure 8.6 : Informations sur le système

La communication entre le programme et le système passe par la ligne de commande, la valeur d'une variable de l'environnement d'exécution et la possibilité de lancer un autre programme. Ces fonctions sont décrites à la figure 8.7.

> `argv` | : | string array
> |  | contient le vecteur de paramètres
> `getenv` | : | string -> string
> |  | demande de la valeur d'une variable
> `command` | : | string -> int
> |  | exécution de la commande passée en argument

> Figure 8.7 : Communication avec le système

Les fonctions de la figure 8.8 permettent une navigation dans la hiérarchie de fichiers.

> `file_exists` | : | string -> bool
> |  | retourne `true` si le fichier existe
> `remove` | : | string -> unit
> |  | destruction d'un fichier
> `rename` | : | string -> string -> unit
> |  | renommage d'un fichier
> `chdir` | : | string -> unit
> |  | change de catalogue courant
> `getcwd` | : | unit -> string
> |  | retourne le nom du catalogue courant

> Figure 8.8 : Manipulation de fichiers

Enfin la gestion des signaux sera décrite au chapitre sur la programmation système (18).
Voici un petit programme qui reprend l'exemple sur la sauvegarde d'une fenêtre graphique sous forme d'un tableau de couleurs. La fonction `main` vérifie qu'elle n'est pas lancée de la boucle d'interaction, lit les noms des fichiers à visualiser sur la ligne de commande, teste s'ils existent et les visualise (avec la fonction `load_screen`). On intercale une attente clavier dans la fenêtre graphique entre deux visualisations.

    # let main () =
       if not (!Sys.interactive) then
         for i = 0 to Array.length(Sys.argv) -1 do
           let nom = Sys.argv.(i) in
             if Sys.file_exists nom then
             begin
               ignore(load_screen nom);
               ignore(Graphics.read_key)
             end
         done;;
    val main : unit -> unit = <fun>

#### Module `Arg`

Le module `Arg` fixe une petite syntaxe pour les arguments de la ligne de commande. Il fournit une fonction permettant l'analyse syntaxique ainsi que la définition d'une action associée aux éléments analysés.
Les divers éléments de la ligne de commande sont séparés par un ou plusieurs espaces. Ils constituent les valeurs stockées dans le tableau `Sys.argv`. Dans la syntaxe fixée par `Arg`, certains éléments distingués commencent par le caractère moins (`-`). On les appelle des mots clés de la ligne de commande. On peut associer aux mots clés une action spécifique qui peut prendre en argument une valeur de type string, int ou float. La valeur de ces arguments est initialisée avec la valeur trouvée sur la ligne de commande juste après le mot clé. Il y a dans ce cas appel à une fonction de conversion des chaînes de caractères vers le type attendu. Les autres éléments de la ligne de commande sont dits arguments anonymes. On leur associe une action commune qui prend leur valeur en argument. Une option non définie provoque l'affichage d'une petite documentation en ligne de la commande dont le contenu est défini par l'utilisateur.
Les actions associées aux mots clés sont encapsulées dans le type :

    type spec =
      | Unit of (unit -> unit)     (* Call the function with unit argument*)
      | Set of bool ref            (* Set the reference to true*)
      | Clear of bool ref          (* Set the reference to false*)
      | String of (string -> unit) (* Call the function with a string
                                    argument *)
      | Int of (int -> unit)       (* Call the function with an int
                                    argument *)
      | Float of (float -> unit)   (* Call the function with a float
                                    argument *)
      | Rest of (string -> unit)   (* Stop interpreting keywords and call the
                                    function with each remaining argument*)

La fonction d'analyse de la ligne de commande est :

    # Arg.parse ;;
    - : (string * Arg.spec * string) list -> (string -> unit) -> string -> unit =
    <fun>

Son premier argument est une liste de triplets de la forme (key, spec, doc) tels que :

- key est une chaîne de caractères correspondant à un mot clé. Elle commence donc par le caractère réservé `'_'`.
- spec est une valeur de type spec spécifiant l'action associée à key.
- doc est une chaîne de caractères décrivant l'option key. Elle est affichée sur une erreur de syntaxe.

Le deuxième argument est la fonction de traitement des arguments anonymes de la ligne de commande. Le dernier argument est une chaîne de caractères affichée en tête de la documentation en ligne.
Le module `Arg` comprend également :

- `Bad` : exception prenant en argument une chaîne de caractères. Elle peut être utilisée par les fonctions de traitement.
- `usage` : de type (string * Arg.spec * string) list -> string -> unit, cette fonction affiche la documentation en ligne. On lui fournira de préférence d les mêmes arguments que ceux de `parse`.
- `current` : de type int ref qui contient une référence sur la valeur courante de l'indice dans le tableau `Sys.argv`. On peut donc modifier cette valeur si besoin est.

À titre d'exemple, nous donnons la fonction `read_args` permettant d'initialiser la configuration du démineur vu au chapitre 6, page ??. Les options possibles seront `-col`, `-lig` et `-min`. Elles seront suivies d'un entier précisant, respectivement : le nombre de colonnes, le nombre de lignes et le nombre de mines désirées. Ces valeurs ne devront pas être inférieures aux valeurs respectives par défaut `10`, `10` et `15`.
Les fonctions de traitement sont :

    # let set_nbcols cf n = cf := {!cf with nbcols = n} ;;
    # let set_nbrows cf n = cf := {!cf with nbrows = n} ;;
    # let set_nbmines cf n = cf := {!cf with nbmines = n} ;;

Elles sont toutes trois de type config ref -> int -> unit. La fonction d'analyse de la ligne de commande peut s'écrire :

    # let read_args() =
      let cf = ref default_config in
      let speclist =
       [("-col", Arg.Int (set_nbcols cf), "nombre de colonnes (>=10)");
        ("-lig", Arg.Int (set_nbrows cf), "nombre de lignes (>=10)");
        ("-min", Arg.Int (set_nbmines cf), "nombre de mines (>=15)")]
      in
      let usage_msg = "usage : demin [-col n] [-lig n] [-min n]" in
       Arg.parse speclist (fun s -> ()) usage_msg; !cf ;;
    val read_args : unit -> config = <fun>

Elle calcule une configuration qui sera passée en argument à la fonction d'ouverture de la fenêtre du jeu `open_wcf` lors du lancement du jeu. Chaque option est, comme son nom l'indique, optionnelle. Si elle ne figure pas sur la ligne de commande, le paramètre correspondant garde la valeur par défaut. L'ordre des options n'a pas d'importance.

#### Module `Filename`

Le module `Filename` donne des opérations sur les noms de fichiers indépendantes des systèmes d'exploitation. En effet les conventions de nommage des fichiers et des catalogues diffèrent fortement entre Windows, Unix et MacOS.

#### Module `Printexc`

Ce module très court (trois fonctions décrites à la figure 8.9) apporte un récupérateur général d'exceptions. Cela est particulièrement pratique pour les programmes exécutés en mode commande3 pour être sûr de ne pas laisser s'échapper une exception qui arrêterait le programme.

> `catch` | : | ('a -> 'b) -> 'a -> 'b
> |  | récupérateur général
> `print` | : | ('a -> 'b) -> 'a -> 'b
> |  | affiche et redéclenche l'exception
> `to_string` | : | exn -> string
> |  | convertit une exception en chaîne

> Figure 8.9 : Récupération d'exceptions

La fonction `catch` applique son premier argument à son second. Cela lancera la fonction principale du programme. Si une exception arrive au niveau de `catch`, c'est à dire n'est pas récupérée à l'intérieur du programme, alors `catch` affichera son nom et sortira du programme. La fonction `print` a le même comportement que `catch` mais redéclenchera l'exception après l'affichage. Enfin la fonction `to_string` convertit une exception en chaîne de caractères. Elle est utilisée par les deux fonctions précédentes. Si on reprend la fonction `main` de visualisation de bitmaps, on écrira alors une fonction d'encapsulation `go` de la manière suivante :

    # let go () =
       Printexc.catch main ();;
    val go : unit -> unit = <fun>

Cela permet de terminer normalement le programme en affichant la valeur de l'exception non capturée.