Photographie/Fabricants/Kowa/Kowa Six
Objective Caml/Modules
Jusqu'à présent, nous avons vu comment il était possible d'utiliser OCaml en mode interactif. Ce mode est très utile pour apprendre OCaml, pour tester des extraits ou des fonctions existantes ou pour écrire des programmes qui se comporteront eux-mêmes comme OCaml en mode interactif. Cependant, ce mode n'est généralement pas approprié pour le développement de logiciels que vous voudrez distribuer, qu'il s'agisse d'applications bureau, d'applications serveur web ou d'utilitaires. Bien entendu, OCaml permet aussi d'écrire et de compiler des logiciels autonomes adaptés à toutes ces situations. Le mécanisme employé pour ce faire porte le nom de "modules".
Les modules ont plusieurs utilités en OCaml. En plus de permettre de produire des applications exécutables, ils permettent aussi de diviser un programme long en plus petites unités, plus simples à gérer, à documenter et à partager entre plusieurs développeurs -- c'est le concept de "modularité". C'est encore ce mécanisme qui est utilisé pour garantir l'indépendance entre parties d'un programme ou pour garantir que certaines parties du programme ne peuvent pas être manipulées par l'extérieur -- c'est la notion d'abstraction.
Ainsi, si nous considérons de nouveau l'exemple des nombres complexes, nous emploierons typiquement les modules pour les raisons suivantes :
De même, dans le cadre des opérations sur les fichiers, la bibliothèque standard d'OCaml utilise des modules pour les raisons suivantes :
Au cours de ce chapitre, nous allons construire et manipuler les modules, ainsi que les informations sur les modules. À partir du chapitre suivant, nous emploierons en permanence les modules fournis avec OCaml Batteries Included.
Objectifs du chapitre.
Ce chapitre vous enseignera
Conventions typographiques.
Dans la suite de cet ouvrage, pour différencier les extraits tapés dans le mode calculatrice des codes sources sauvegardés dans des fichiers, nous emploierons deux styles différents.
Pour le mode calculatrice, nous noterons :
Le même programme, une fois sauvegardé, sera noté avec le style suivant :
print_endline "Bonjour, le monde" ;;
Tout comme le symbole codice_1 du mode calculatrice ne doit pas être effectivement tapé, les numéros de lignes ne font pas partie du code source.
Logiciels utilisés au cours de ce chapitre.
Compilation.
Comme nous l'avons mentionné précédemment, OCaml est un langage "compilé", c'est-à-dire un langage qui dispose des outils nécessaires pour transformer un code source en un fichier exécutable autonome. Plusieurs outils de compilation existent, qui à partir d'un fichier source peuvent produire soit un exécutable portable mais non-optimisé, comparable à du code compilé Java (codice_2 et codice_3), soit un exécutable optimisé mais réservé à une plate-forme, comparable à du code compilé C (codice_4 et codice_5), soit du code source utilisable avec une autre syntaxe (codice_6, codice_7, codice_8, codice_9), soit encore un exécutable modifié pour permettre l'analyse de performances (codice_10), de la documentation (codice_11) etc. 
L'outil principal que nous allons manipuler dans le restant de cet ouvrage est OCamlBuild, dont le rôle est d'automatiser la construction d'un exécutable en gérant intelligemment tous les programmes précédents. Cet outil, comme les précédents, est fourni avec OCaml.
La manière la plus simple de compiler un fichier codice_12 à l'aide d'OCamlBuild est, dans une ligne de commande, de taper
ocamlfind batteries/ocamlbuild fichier_de_destination
Cette commande invoque la version de OCamlBuild pour OCaml Batteries Included en lui indiquant que nous voulons que, à l'issue de la compilation, le fichier codice_13 soit créé. Selon le nom du fichier de destination, la compilation produira soit un fichier exécutable, soit une des informations résumant le contenu du programme, soit une documentation sous la forme d'une page web, etc.
Dans tous les cas, OCamlBuild examine tous les fichiers OCaml du répertoire, détermine les fichiers qui sont nécessaires pour compiler vos fichiers, lesquels de ces fichiers ont été modifiés et doivent donc être recompilés, puis se charge des étapes de compilation et de liaison. En cas de problème de compilation, OCamlBuild affiche des messages d'erreur comparables à celles de la ligne de commande OCaml.
Pour la majorité des projets, cette utilisation d'OCamlBuild suffit. Nous verrons plus tard comment demander à OCamlBuild de ne construire que certaines parties d'un projet incomplet ou comment générer de la documentation depuis un fichier source.
Édition de code source.
Vous pouvez écrire vos programmes OCaml avec n'importe quel "éditeur de texte" pour langues occidentales. Par contre, Word ou autre "traitement de texte" ne vous servira à rien.
Emacs 22.
Emacs est un éditeur de texte pour programmeurs, extrêmement puissant, lui-même programmable et disposant de centaines d'extensions pour des langages de programmation ou des situations particulières. Emacs est disponible gratuitement sur à peu près toutes les plate-formes existantes.
Pour utiliser Emacs avec OCaml, vous aurez besoin de
Une fois que vous avez installé et configuré Emacs et Tuareg, Emacs reconnaîtra et lancera automatiquement Tuareg dès que vous ouvrirez un fichier dont l'extension est codice_14 ou codice_15. 
Exécuter un extrait depuis Emacs.
Une fois Tuareg lancé, vous pouvez demander au mode interactif de OCaml d'exécuter une commande en plaçant votre curseur sur la commande et en appliquant la combinaison de touches Contrôle-C-E. La première fois que vous invoquerez le mode interactif, Emacs vous demandera quelle version d'OCaml vous voulez employer.
Répondez ocamlfind batteries/ocaml
Compiler un projet depuis Emacs.
De même, une fois Tuareg lancé, vous pouvez demander à Emacs de compiler les fichiers sur lesquels vous travaillez. Pour ce faire, appliquez la combinaison de touches Contrôle-C-C. La première fois que vous invoquerez le compilateur, Emacs vous demandera quel compilateur vous voulez employer.
Répondez ocamlfind batteries/ocamlbuild XXXX où codice_16 est le nom du fichier compilé que vous souhaitez obtenir.
VI / Vim.
VI / Vim est un autre éditeur de textes pour programmeurs, tout aussi puissant, programmable, extensible et disponible.
Pour utiliser VI avec OCaml, vous aurez besoin de
Si vous pensez que vous serez amené à éditer des fichiers en ssh sur un serveur, vim est un meilleur choix que emacs.
Programmes complets.
OCaml a une définition très simple de ce qu'est un programme complet : tout extrait de OCaml qui fonctionne en mode calculatrice, une fois sauvegardé dans un fichier avec l'extension .ml, est un programme complet valide.
Ainsi,
print_endline "Bonjour, le monde";;
est un programme complet. On peut même d'ailleurs supprimer le codice_17, qui n'est pas utile dans ce cas précis.
print_endline "Bonjour, le monde !"
Pour utiliser ce programme, commençons le sauvegarder, sous le nom codice_18.
Lancer un programme.
Nous pouvons maintenant le lancer notre programme ; pour ce faire :
$ ocamlfind batteries/ocaml bonjour.ml
vous verrez s'afficher
Bonjour, le monde !
Félicitations, vous venez de lancer votre premier programme OCaml.
Compiler et exécuter un programme.
Il existe en fait plusieurs manières de lancer un programme OCaml. La manière directe, que nous venons de voir, s'appelle l'interprétation : OCaml lit le programme, vérifie qu'il a un sens, puis l'exécute immédiatement. Il s'agit de la manière la plus immédiate de tester si un programme s'exécute correctement mais le prix à payer est que le programme s'exécute plus lentement qu'avec les autres méthodes. De plus, dans un certain nombre de cas, il n'est pas raisonnable de demander à l'utilisateur, à chaque fois qu'il veut lancer son programme, de taper une ligne de code aussi surprenante que
$ ocamlfind batteries/ocaml bonjour.ml
L'alternative consiste à compiler le programme, c'est-à-dire à demander à OCaml de transformer votre fichier codice_18 en un autre fichier que l'ordinateur comprendra directement. Pour ce faire, toujours dans un terminal, tapez l'une des lignes suivantes :
$ ocamlfind batteries/ocamlbuild bonjour.byte
ou
$ ocamlfind batteries/ocamlbuild bonjour.native
La première de ces lignes demande à OCamlBuild de construire codice_24, c'est-à-dire un fichier exécutable portable, petit, rapide à compiler et théoriquement capable de fonctionner sur n'importe quel type d'ordinateur. La deuxième ligne demande à OCamlBuild de construire codice_25, c'est-à-dire un fichier exécutable optimisé pour un ordinateur précis et donc inutilisable sur tout autre type d'ordinateur, plus lent à compiler mais plus rapide à l'exécution.
Dans le reste de cet ouvrage, nous utiliserons généralement des fichiers portables (donc ici codice_24). Ce choix est purement arbitraire.
Dans les deux cas, sauf accident, vous verrez apparaître un indicateur de progression puis, à l'issue de la compilation, un message de la forme
Finished, 3 targets (0 cached) in 00:00:00.
Ceci signifie qu'OCamlBuild a du produire 3 fichiers (dont codice_24), qu'aucun de ces fichiers n'avait déjà été produit lors d'une compilation précédente, et que le temps total de compilation a été arrondi à 0 secondes.
Anecdote OCamlBuild est un peu optimiste dans son affichage des durées de compilation.
Pour lancer le programme compilé, sous MacOS X ou Linux, il suffit alors d'écrire, toujours dans le terminal :
$ ./bonjour.byte
ou, sous Windows 
> bonjour.byte
De nouveau, le symbole codice_21 ou codice_29 est inscrit par votre terminal, ce n'est pas à vous de le recopier.
Vous obtiendrez alors
Bonjour, le monde !
Théoriquement, vous pourriez aussi ouvrir le programme depuis l'Explorateur de fichiers (sous Windows), le Finder (sous MacOS X), Konqueror, Nautilus ou votre gestionnaire de fichiers préféré (sous Linux), en ouvrant le répertoire qui contient codice_24 et en double-cliquant sur l'icône correspondante. Pour ce programme, qui se referme immédiatement dès qu'il a affiché une phrase, cela ne servirait à rien. Cependant, une fois que vous aurez doté votre programme d'une interface graphique ou textuelle, ce sera une manière de le lancer.
Note Il existe encore d'autres manières d'exécuter un programme OCaml, notamment la possibilité de transformer votre fichier codice_18 en un "script exécutable". Cette fonctionnalité n'est quasiment jamais utilisée car le programme ainsi lancé reste interprété et donc beaucoup plus lent qu'un programme compilé. Nous ne détaillerons donc pas la technique.
Effets et résultats.
Si nous avions tapé le contenu de codice_18 depuis le mode calculatrice, nous aurions obtenu quelque chose comme :
Bonjour, le monde !
- : unit = ()
Ici, comme nous venons de le voir, le lancement de codice_18, codice_24 ou codice_25 a uniquement causé l'affichage de
Bonjour, le monde !
De la même manière, dans le mode calculatrice, le nombre 5 est une expression valide, qui produira
- : int = 5
Par contre, si nous exécutons le programme correspondant (donc un fichier qui contiendra en tout et pour tout 5), rien ne s'affichera. Cela ne signifiera pas qu'il y a eu le moindre problème -- de fait, le programme se sera exécuté correctement et aura un résultat. Par contre, comme nous n'avons pas précisé dans le programme que nous voulions afficher ce résultat, le programme n'aura aucun effet visible.
Pour faire afficher le résultat, nous aurions du écrire le programme suivant :
print_int 5
À l'exécution, ce programme calculera 5 et aura pour effet d'afficher cette valeur. Comme codice_36 a pour type codice_37, ce programme aura de plus pour résultat codice_38, résultat qui ne sera lui-même pas affiché.
Vocabulaire On désigne sous le nom d'effet observable ou, tout simplement, effet toute influence visible du programme sur le reste du monde. Ainsi, afficher un texte, ouvrir une boîte de dialogue, modifier un fichier ou envoyer un mail constituent autant d'effets. Par opposition, calculer le résultat d'une expression arithmétique (sans l'afficher) ou calculer la longueur d'une liste (sans l'afficher) n'a aucun effet. Par extension, on considère que lire un fichier, réagir à un clic ou télécharger une page web sont encore des effets.
Précautions et limitations.
Séparateurs.
Dans le mode calculatrice, le symbole codice_17 sert à prévenir OCaml que vous avez fini de taper. Dans un fichier .ml, OCaml considère que vous avez fini de taper une fois qu'il atteint la fin du fichier. L'utilisation du codice_17 est donc beaucoup plus rare dans un fichier .ml que dans le mode calculatrice. On s'en sert sert surtout pour séparer deux expressions.
Ainsi, le programme suivant est tout à fait légitime
5 + 2;;
print_endline "Bonjour, le monde"
Ce programme commencera par calculer le résultat de codice_41, résultat qu'il n'affichera pas, puis il affichera le texte codice_42.
Fichiers portables.
Sur le principe, les fichiers exécutables portables codice_43 que nous venons de voir et les fichiers modules portables codice_44 que nous verrons bientôt sont comparables aux fichiers portables codice_45 de Java. 
En raison de priorités de développement différentes dans la conception du langage, les fichiers portables de OCaml sont malheureusement moins portables que leurs homologues en Java, puisque, s'ils sont compatibles avec tous les types d'ordinateur, ils ne sont compatibles qu'avec la version de OCaml et des bibliothèques de programmation qui ont servi à les construire.
L'une des conséquences de ces choix de développement est que, le plus souvent, un programme OCaml sera distribué soit sous la forme d'un exécutable natif optimisé, qui sera indépendant des bibliothèques installées, soit sous la forme de code source.
Modules.
Comme la quasi-totalité des langages de programmation modernes, OCaml Batteries Included est fourni avec un grand nombre de structures de données et de fonctions de manipulation de données, sous la forme d'une bibliothèque standard. Ces structures et fonctions sont rassemblées selon leur rôle en modules. Ainsi, les fonctions qui servent à manipuler des listes forment le module codice_47, les fonctions qui servent à afficher des informations à l'écran forment le module codice_48, les fonctions de manipulation de texte encodé en "Latin-1" (caractères européens) sont rassemblées dans le module codice_49, les fonctions de manipulation de texte encodé en UTF-8 (caractères internationaux) sont rassemblées dans le module codice_50, etc. Au cours de cette section, nous allons voir comment utiliser les fonctions d'un module puis comment créer de nouveaux modules.
Note En OCaml, les noms de modules commencent toujours par une majuscule. Ceci est une obligation du langage.
Utilisation d'un module.
De base, OCaml est fourni avec plus d'une centaine de fonctions de manipulation des listes. Ainsi, pour calculer la longueur d'une liste, on invoquera la fonction codice_51, c'est-à-dire la fonction codice_52 du module codice_47 :
- : 'a list -> int = <fun>
De même, pour déterminer le dernier élément d'une liste, on emploiera la fonction codice_54, c'est-à-dire la fonction codice_55 du module codice_47 :
- : 'a list -> 'a = <fun>
codice_51 et codice_54 sont des fonctions parfaitement ordinaires, qui peuvent être manipulées comme toutes les autres fonctions. Ainsi, on écrira :
val longueur : 'a list -> int = <fun>
De la même manière, le module codice_47 définit un type codice_60, qui est identique au type codice_61 et qui peut se manipuler comme tout autre type :
type associations = (string * int) Batteries.List.t
Nous reviendrons plus tard sur la raison pour laquelle OCaml a répondu codice_62 et non codice_63. Pour le moment, nous nous contenterons d'accepter que codice_64 et codice_65 désignent bien les mêmes types.
Pour consulter la documentation sur un module, vous pouvez employer la directive codice_66. Comme toutes les directives, codice_66 ne peut être utilisée que depuis le mode calculatrice, comme suit :
Vous pouvez aussi consulter directement la documentation d'une des valeurs ou d'un des types du module
Vous pouvez aussi consulter la liste des fonctions et types d'un module (sans documentation) à l'aide de la directive codice_68, comme suit :
module List :
 sig
 type 'a t = 'a list
 type 'a enumerable = 'a t
 type 'a mappable = 'a t
 val length : 'a list -> int
 val hd : 'a list -> 'a
 val tl : 'a list -> 'a list
 val is_empty : 'a list -> bool
 val cons : 'a -> 'a list -> 'a list
 val first : 'a list -> 'a
 val last : 'a list -> 'a
 val at : 'a list -> int -> 'a
 val rev : 'a list -> 'a list
 val append : 'a list -> 'a list -> 'a list
 val rev_append : 'a list -> 'a list -> 'a list
 val concat : 'a list list -> 'a list
 val flatten : 'a list list -> 'a list
 val make : int -> 'a -> 'a list
 val init : int -> (int -> 'a) -> 'a list
 val iter : ('a -> unit) -> 'a list -> unit
 val iteri : (int -> 'a -> 'b) -> 'a list -> unit
 val map : ('a -> 'b) -> 'a list -> 'b list
 val mapi : (int -> 'a -> 'b) -> 'a list -> 'b list
 val rev_map : ('a -> 'b) -> 'a list -> 'b list
 val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
 val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b
 val reduce : ('a -> 'a -> 'a) -> 'a list -> 'a
 val max : 'a list -> 'a
 val min : 'a list -> 'a
 val iter2 : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit
 val map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
 val rev_map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
 val fold_left2 : ('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a
 val fold_right2 :
 ('a -> 'b -> 'c -> 'c) -> 'a list -> 'b list -> 'c -> 'c
 val for_all : ('a -> bool) -> 'a list -> bool
 val exists : ('a -> bool) -> 'a list -> bool
 val for_all2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool
 val exists2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool
 val mem : 'a -> 'a list -> bool
 val memq : 'a -> 'a list -> bool
 val find : ('a -> bool) -> 'a list -> 'a
 val find_exn : ('a -> bool) -> exn -> 'a list -> 'a
 val findi : (int -> 'a -> bool) -> 'a list -> int * 'a
 val find_map : ('a -> 'b option) -> 'a list -> 'b
 val rfind : ('a -> bool) -> 'a list -> 'a
 val filter : ('a -> bool) -> 'a list -> 'a list
 val filter_map : ('a -> 'b option) -> 'a list -> 'b list
 val find_all : ('a -> bool) -> 'a list -> 'a list
 val partition : ('a -> bool) -> 'a list -> 'a list * 'a list
 val index_of : 'a -> 'a list -> int option
 val index_ofq : 'a -> 'a list -> int option
 val rindex_of : 'a -> 'a list -> int option
 val rindex_ofq : 'a -> 'a list -> int option
 val unique : ?cmp:('a -> 'a -> bool) -> 'a list -> 'a list
 val assoc : 'a -> ('a * 'b) list -> 'b
 val assoc_inv : 'a -> ('b * 'a) list -> 'b
 val assq : 'a -> ('a * 'b) list -> 'b
 val mem_assoc : 'a -> ('a * 'b) list -> bool
 val mem_assq : 'a -> ('a * 'b) list -> bool
 val remove_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list
 val remove_assq : 'a -> ('a * 'b) list -> ('a * 'b) list
 val split_at : int -> 'a list -> 'a list * 'a list
 val split_nth : int -> 'a list -> 'a list * 'a list
 val remove : 'a list -> 'a -> 'a list
 val remove_if : ('a -> bool) -> 'a list -> 'a list
 val remove_all : 'a list -> 'a -> 'a list
 val take : int -> 'a list -> 'a list
 val drop : int -> 'a list -> 'a list
 val take_while : ('a -> bool) -> 'a list -> 'a list
 val takewhile : ('a -> bool) -> 'a list -> 'a list
 val drop_while : ('a -> bool) -> 'a list -> 'a list
 val dropwhile : ('a -> bool) -> 'a list -> 'a list
 val interleave : ?first:'a -> ?last:'a -> 'a -> 'a list -> 'a list
 val enum : 'a list -> 'a Enum.t
 val of_enum : 'a Enum.t -> 'a list
 val backwards : 'a list -> 'a Enum.t
 val of_backwards : 'a Enum.t -> 'a list
 val split : ('a * 'b) list -> 'a list * 'b list
 val combine : 'a list -> 'b list -> ('a * 'b) list
 val make_compare : ('a -> 'a -> int) -> 'a list -> 'a list -> int
 val sort : ?cmp:('a -> 'a -> int) -> 'a list -> 'a list
 val stable_sort : ('a -> 'a -> int) -> 'a list -> 'a list
 val fast_sort : ('a -> 'a -> int) -> 'a list -> 'a list
 val merge : ('a -> 'a -> int) -> 'a list -> 'a list -> 'a list
 exception Empty_list
 exception Invalid_index of int
 exception Different_list_size of string
 val t_of_sexp : (Sexplib.Sexp.t -> 'a) -> Sexplib.Sexp.t -> 'a t
 val sexp_of_t : ('a -> Sexplib.Sexp.t) -> 'a t -> Sexplib.Sexp.t
 val print :
 ?first:string ->
 ?last:string ->
 ?sep:string ->
 ('a IO.output -> 'b -> unit) ->
 'a IO.output -> 'b t -> unit
 val sprint :
 ?first:string ->
 ?last:string ->
 ?sep:string ->
 ('a IO.output -> 'b -> unit) -> 'b t -> string
 val nth : 'a list -> int -> 'a
 module Exceptionless :
 sig
 val rfind : ('a -> bool) -> 'a list -> 'a option
 val findi : (int -> 'a -> bool) -> 'a list -> (int * 'a) option
 val split_at :
 int ->
 'a list -> [ `Invalid_index of int | `Ok of 'a list * 'a list ]
 val at : 'a list -> int -> [ `Invalid_index of int | `Ok of 'a ]
 val assoc : 'a -> ('a * 'b) list -> 'b option
 val assoc_inv : 'a -> ('b * 'a) list -> 'b option
 val assq : 'a -> ('a * 'b) list -> 'b option
 end
 module Labels :
 sig
 val init : int -> f:(int -> 'a) -> 'a list
 val iter : f:('a -> unit) -> 'a list -> unit
 val iteri : f:(int -> 'a -> 'b) -> 'a list -> unit
 val map : f:('a -> 'b) -> 'a list -> 'b list
 val mapi : f:(int -> 'a -> 'b) -> 'a list -> 'b list
 val rev_map : f:('a -> 'b) -> 'a list -> 'b list
 val fold_left : f:('a -> 'b -> 'a) -> init:'a -> 'b list -> 'a
 val fold_right : f:('a -> 'b -> 'b) -> 'a list -> init:'b -> 'b
 val iter2 : f:('a -> 'b -> unit) -> 'a list -> 'b list -> unit
 val map2 : f:('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
 val rev_map2 : f:('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list
 val fold_left2 :
 f:('a -> 'b -> 'c -> 'a) -> init:'a -> 'b list -> 'c list -> 'a
 val fold_right2 :
 f:('a -> 'b -> 'c -> 'c) -> 'a list -> 'b list -> init:'c -> 'c
 val for_all : f:('a -> bool) -> 'a list -> bool
 val exists : f:('a -> bool) -> 'a list -> bool
 val for_all2 : f:('a -> 'b -> bool) -> 'a list -> 'b list -> bool
 val exists2 : f:('a -> 'b -> bool) -> 'a list -> 'b list -> bool
 val find : f:('a -> bool) -> 'a list -> 'a
 val find_exn : f:('a -> bool) -> exn -> 'a list -> 'a
 val findi : f:(int -> 'a -> bool) -> 'a list -> int * 'a
 val rfind : f:('a -> bool) -> 'a list -> 'a
 val filter : f:('a -> bool) -> 'a list -> 'a list
 val filter_map : f:('a -> 'b option) -> 'a list -> 'b list
 val find_all : f:('a -> bool) -> 'a list -> 'a list
 val partition : f:('a -> bool) -> 'a list -> 'a list * 'a list
 val remove_if : f:('a -> bool) -> 'a list -> 'a list
 val take_while : f:('a -> bool) -> 'a list -> 'a list
 val drop_while : f:('a -> bool) -> 'a list -> 'a list
 val stable_sort : ?cmp:('a -> 'a -> int) -> 'a list -> 'a list
 val fast_sort : ?cmp:('a -> 'a -> int) -> 'a list -> 'a list
 val merge : cmp:('a -> 'a -> int) -> 'a list -> 'a list -> 'a list
 end
Vocabulaire L'information qui vient d'être affichée sur le module codice_47 et qui commence par codice_70 et s'achève par codice_71 s'appelle la "signature" du module. La notion de signature généralise aux modules la notion de "type" d'une valeur.
Vous constaterez que certaines fonctions du module codice_47 font appel à des types d'autres modules codice_73 et codice_74. Nous nous intéresserons à ces modules dans des chapitres ultérieurs. Vous constaterez aussi que le module codice_47 contient deux sous-modules codice_76 et codice_77, dont nous parlerons plus tard. Pour le moment, il suffit de savoir qu'un module peut contenir un sous-module. Comme pour les valeurs et les types, le nom complet d'un sous-module est le nom du module qui le contient, suivi d'un point, suivi du nom local du sous-module. Ainsi, le module codice_76 que nous avons sous les yeux a pour nom complet codice_79 et le module codice_77 aura pour nom complet codice_81.
Ouverture de module.
Considérons la fonction suivante :
 let l' = List.unique l in
 List.take (List.length l' / 2) l';;
val second_half : 'a list -> 'a list = <fun>
Cette fonction prend en argument une list codice_82, en extrait la liste codice_83 des valeurs uniques de codice_82 (c'est-à-dire supprime les doublons), puis calcule la longueur de codice_83, la divise par deux et se sert de cette valeur pour renvoyer la première moitié de la liste codice_83. Si cette fonction n'est probablement guère utile en pratique, nous pouvons constater qu'elle fait appel trois fois au préfixe codice_87, peut-être aux dépens de la lisibilité. 
Il serait certainement plus agréable de n'avoir à préciser qu'une seule fois que nous manipulons des fonctions du module codice_47. C'est à cela que sert la commande codice_89. Nous l'utiliserons comme suit :
 let l' = unique l in
 take (length l' / 2) l';;
val second_half : 'a list -> 'a list = <fun>
Ou encore
 open List in
 let l' = unique l in
 take (length l' / 2) l';;
val second_half : 'a list -> 'a list = <fun>
Dans ces deux extraits, nous n'avons eu à mentionner codice_47 qu'une seule fois. Respectivement, ces extraits sont équivalents à :
 let unique = List.unique
 let take = List.take
 let length = List.length;;
 let l' = unique l in
 take (length l' / 2) l';;
val second_half : 'a list -> 'a list = <fun>
ou à 
 let unique = List.unique
 and take = List.take
 and length = List.length
 in
 let l' = unique l in
 take (length l' / 2) l';;
val second_half : 'a list -> 'a list = <fun>
Dans tous les cas, la fonction codice_91 obtenue est identique à celle que nous avions initialement. La seule différence est la notation plus légère.
Vocabulaire L'opération codice_92 est l'ouverture du module codice_47. L'opération codice_94 est l'ouverture locale du module codice_47.
Pour des raisons de confort de notation, il est aussi possible d'ouvrir plusieurs modules en une seule opération. Ainsi, pour ouvrir le module codice_47 et le module codice_49, on pourra écrire
On utilisera fréquemment ce mécanisme pour ouvrir en une opération le module codice_47 et son sous-module codice_76, qui redéfinit certaines opérations de codice_47 pour permettre d'employer des notations plus robustes (mais plus lourdes), comme suit :
Nous reviendrons dans un chapitre ultérieur sur le rôle exact de ce module codice_76.
Le module Standard.
Au cours des chapitres précédents, nous avons manipulé un certain nombre de fonctions sans préciser de quel module elles sont tirées : codice_102, codice_103, codice_104 etc. Ces fonctions et bien d'autres viennent d'un module spécial nommé codice_105, qui est automatiquement ouvert. Ce module définit aussi de nombreuses fonctions de gestion des erreurs, d'affichage, de manipulation de fichiers, de calculs sur les entiers ou les nombres flottants, etc.
Comme pour les autres modules, vous pouvez consulter le contenu du module codice_105 à l'aide de codice_68 ou codice_66.
Précautions et limitations.
Majuscules.
Insistons sur le fait qu'un nom de module commence toujours par une majuscule. Ainsi, si vous écrivez codice_51, OCaml sait qu'il doit chercher la fonction codice_52 du module codice_47 et, si nécessaire, préalablement charger ce module. À l'inverse, si vous écrivez codice_112, OCaml sait qu'il doit chercher le champ codice_52 d'un enregistrement nommé codice_114, ce qui est une opération beaucoup plus simple.
En particulier, les deux constructions produiront des messages différents :
Error: Unbound value List.longueur
Error: Unbound value Liste.longueur
Error: Unbound value liste
Constructeurs et modules.
De la même manière qu'un nom de valeur ou de type, un nom de constructeur doit être préfixé par le nom du module qui le contient.
Ainsi, le module codice_115 définit de nombreux constructeurs, un pour chaque erreur qui peut avoir lieu lors d'une interaction avec le système d'exploitation. Parmi ces erreurs, citons arbitrairement codice_116, qui peut être invoquée lorsque le programme tente d'accéder à une ressource qui est déjà occupée par un autre logiciel. Comme nous pouvons le constater, le nom complet de cette ressource est codice_117:
Error: Unbound constructor EBUSY
- : Batteries.Unix.error = Batteries.Unix.EBUSY
- : Batteries.Unix.error = Batteries.Unix.EBUSY
Ceci est aussi valable pour les enregistrements. Ainsi, le module codice_118 définit une structure codice_119. Le nom complet des champs est en fait codice_120 et codice_121. Pour nous en convaincre, voici un exemple
Error: Unbound record field label re
- : Batteries.Complex.t =
Type t.
Nous avons vu que le module codice_47 définit un type codice_60 (le type des listes contenant des éléments de type codice_124, c'est-à-dire un synonyme de codice_61). De la même manière, le module codice_126 définit un type codice_127 (le type des entiers, c'est-à-dire un synonyme de codice_128), le module codice_129 définit un type codice_127 (le type des textes contenant des caractères internationaux au standard Unicode), etc. De fait, une très grande partie des modules de OCaml Batteries Included définissent un type codice_127. Il s'agit d'une convention de nommage simple : si un module est conçu spécialement pour accueillir une structure de données, le nom du type correspondant est codice_127.
Afin d'éviter de confondre tous ces types qui portent le même nom, on prendra l'habitude d'utiliser le nom complet de chaque type, donc codice_133, etc.
Batteries.
Revenons sur un de nos exemples :
type associations = (string * int) Batteries.List.t
Comme nous pouvons le constater, OCaml a accepté cette définition mais la réponse mentionne codice_62 et non codice_63. Nous laissons au lecteur le soin de vérifier que ces deux types sont bien identiques.
À quoi est du ce renommage ?
De fait, le nom complet du module codice_47 est codice_137, ce qui signifie que le module codice_47 fait partie du module codice_139. Ce dernier est un module spécial, qui contient tous les autres modules de OCaml Batteries Included, y compris codice_105 et dont vous n'avez jamais besoin de taper le nom. Nous aurions cependant tout aussi bien pu taper :
type associations = (string * int) Batteries.List.t
En général, nous éviterons de mentionner explicitement codice_139, pour de simples raisons de lisibilité.
Ouverture et écrasement.
Comme nous l'avons mentionné plus haut, l'ouverture d'un module définit implicitement de nombreuses valeurs. Ainsi, nous aurons
val length : int = 6
- : int = 6
- : 'a list -> int = <fun>
Dans cet extrait, la constante codice_52 a été écrasée par codice_51 lors de l'ouverture. Si ce comportement est parfois désiré, il s'agit aussi d'une source d'erreurs assez fréquente. Pour éviter tout accident, on préférera fréquemment employer l'ouverture locale, moins fragile puisqu'elle ne déborde pas d'un domaine visible au premier coup d'œil.
Créer de nouveaux modules.
Bien entendu, il est possible d'écrire de nouveaux modules et de les utiliser comme les modules d'OCaml Batteries Included. Ceci se fait en deux étapes : définir le "corps" du module (c'est-à-dire les fonctionnalités du module) puis, si nécessaire, sa "signature" (c'est-à-dire les restrictions sur l'usage du module). 
Commençons par nous intéresser au corps du module.
Créer un nouveau module peut se faire aussi simplement que d'écrire un programme complet. De fait, la seule différence entre un programme complet et un module est la manière de le compiler. Ainsi, pour créer un module on commencera par sauvegarder du code source dans un fichier d'extension .ml, que l'on compilera vers un fichier d'extension .cmo (pour obtenir un module portable non-optimisé) ou .cmx (pour obtenir un module portable optimisé). De plus, si vous ne souhaitez utiliser votre module que dans un programme compilé, vous n'avez pas besoin de le compiler du tout, OCamlBuild s'en chargera pour vous.
Ainsi, si nous souhaitons placer dans un module les fonctions que nous avons définies précédemment sur les couleurs de cartes codice_148. Comme ce module servira uniquement à accueillir une structure de données qui permettra de manipuler les couleurs de cartes, profitons-en pour renommer notre type de codice_149 en codice_127. Pour respecter les conventions de nommage habituelles des modules, nous en profiterons pour renommer quelques fonctions.
type t = 
 | Trefle
 | Pique
 | Coeur
 | Carreau ;;
let to_string = function
 | Trefle -> "trefle"
 | Pique -> "pique"
 | Coeur -> "coeur"
 | Carreau-> "carreau" ;;
De la même manière, nous pouvons définir un nouveau module qui contiendra les fonctions de manipulation des cartes elles-mêmes. Appelons le fichier codice_151. Ce module utilisera le module codice_152, défini à l'aide du fichier précédent. Profitons-en de même pour renommer codice_127 notre type de cartes.
type t = 
 | Atout of int
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of int * Couleur.t ;;
let string_of_chiffre_francais = function
 | 1 -> "Un"
 | 2 -> "Deux"
 | 3 -> "Trois"
 | 4 -> "Quatre"
 | 5 -> "Cinq"
 | 6 -> "Six"
 | 7 -> "Sept"
 | 8 -> "Huit"
 | 9 -> "Neuf"
 | 0 -> "Zero" 
 | n -> string_of_int n ;;
let to_string carte = 
 open Couleur in match carte with
 | Atout i -> string_of_int i ^ " d'atout"
 | Roi c -> "Roi de " ^ ( to_string c ) (*Notre définition de to_string utilise Couleur.to_string*)
 | Dame c -> "Dame de " ^ ( to_string c )
 | Cavalier c -> "Cavalier de " ^ ( to_string c )
 | Valet c -> "Valet de " ^ ( to_string c )
 | As c -> "As de " ^ ( to_string c )
 | Nombre (i, c) -> string_of_chiffre_francais i ^ " de " ^ ( to_string c ) ;;
let get_couleur = function
 | As c -> Some c
 | Roi c -> Some c
 | Valet c -> Some c
 | Dame c -> Some c
 | Cavalier c -> Some c
 | Nombre _ c -> Some c
 | _ -> None ;;
Si nous le souhaitons, nous pouvons maintenant compiler nos modules :
$ ocamlfind batteries/ocamlbuild couleur.cmo carte.cmo
Finished, 4 targets (0 cached) in 00:00:02.
Note L'ordre codice_154 codice_155 est sans importance. Si un module dépend d'un autre, OCamlBuild compilera les modules dans l'ordre de dépendances.
Rappelons que nous n'avons pas besoin de compiler les modules nous-mêmes, à moins d'avoir envie de nous en servir en mode interactif, car OCamlBuild les compilera lui-même si nécessaire.
Testons maintenant nos modules dans un programme :
let premiere_carte_qui_porte_malheur = Carte.As Couleur.Pique;;
let deuxieme_carte_qui_porte_malheur = Carte.Atout 16;;
print_endline ("La première carte qui porte malheur est "^Carte.to_string premiere_carte_qui_porte_malheur);;
print_endline ("La deuxième carte qui porte malheur est "^Carte.to_string deuxieme_carte_qui_porte_malheur);;
Compilons et testons :
$ ocamlfind batteries/ocamlbuild test.byte
Finished, 7 targets (4 cached) in 00:00:01.
$ ./test.byte
La première carte qui porte malheur est As de pique
La deuxième carte qui porte malheur est 16 d'atout
Charger un module.
Une fois qu'un fichier .ml a été compilé sous la forme d'un codice_44, nous pouvons le charger dans le mode interactif. Pour ce faire, nous utiliserons les directives codice_157 (pour aller dans le répertoire qui contient le fichier codice_44) et codice_159 (pour charger effectivement le fichier codice_44). Si un codice_44 dépend d'un autre codice_44, les fichiers doivent être chargés par ordre de dépendance.
Par défaut, OCamlBuild sauvegarde les fichiers codice_44 dans un répertoire nommé codice_164. Nous pouvons donc faire
Error: Unbound constructor Atout
Error: Unbound constructor Carte.Atout
Error: Unbound constructor Atout
- : Carte.t = Carte.Atout 16
Comme l'indique ce dernier extrait, charger un fichier codice_44 n'ouvre pas automatiquement le module. Il est donc possible de charger un module sans crainte d'écraser les noms de fonctions ou de types existants.
Une autre manière d'ouvrir un ou plusieurs fichiers .cmo est de donner la liste sur la ligne de commande
$ ocamlfind batteries/ocaml couleur.cmo carte.cmo
 _________________________________
 [| + | | Batteries Included - |
 |_______|_|_______________________|
 _________________________________
 | - Type '#help;;' | | + |]
 |_______________________|_|_______|
- : Carte.t = Carte.Atout 16
Vous disposez maintenant de toutes les connaissances nécessaires pour créer et utiliser un module. Dans la section suivante, nous allons voir comment et pourquoi restreindre l'accès à un module.
Vocabulaire Indépendamment du langage de programmation, le fait de concevoir un programme comme un ensemble de modules dotés de dépendances simples et précises s'appelle la modularité.
Précautions et limitations.
Un programme est un module.
Insistons sur un point : un module peut contenir exactement le même genre de choses qu'un programme. Le fichier suivant, sauvegardé sous le nom codice_18 peut donc être compilé comme un module tout à fait acceptable :
print_endline "Bonjour, le monde !";;
Il s'agit d'un module qui ne définit aucune fonction, aucun type, mais qui, lorsqu'il est utilisé par un programme, affiche codice_167.
Pour nous en convaincre, commençons par le compiler :
$ ocamlfind batteries/ocamlbuild bonjour.cmo
Finished, 3 targets (0 cached) in 00:00:00.
Puis chargeons ce fichier en mode interactif
Bonjour, le monde !
Ordre de chargement.
Insistons sur un point important : si les fichiers codice_44 peuvent être compilés dans n'importe quel ordre, ils doivent être chargés par ordre de dépendance, c'est-à-dire en ne chargeant que des fichiers codice_44 qui dépendent de modules déjà chargés.
Ainsi, l'extrait suivant charge les modules dans le bon sens :
Par contre, si nous essayons de charger codice_155 avant codice_154, nous obtiendrons :
Error: Reference to undefined global `Couleur'
Ce choix de conception du langage permet de garantir qu'un programme qui se charge peut aussi s'exécuter. Par contraste, des langages tels que Python offrent plus de souplesse au niveau du chargement mais un programme une fois lancé peut encore s'arrêter en catastrophe lorsque des modules s'avèrent manquants.
Dépendances cycliques.
Une conséquence directe de cette contrainte sur l'ordre de chargement est qu'il n'est pas possible, en OCaml, d'écrire deux modules qui dépendent chacun de l'autre. Ainsi, il est impossible de compiler les deux fichiers suivants ensemble :
let a = 5;;
let b = B.b;;
let a = A.a;;
let b = 6;;
Si nous essayons de compiler l'un ou l'autre, nous obtiendrons
$ ocamlfind batteries/ocamlbuild a.cmo
Circular build detected (b.cmi already seen in [ a.cmi; b.cmi; a.cmo ])
Compilation unsuccessful after building 2 targets (0 cached) in 00:00:00.
Vocabulaire On appelle ce type de problème une "dépendance cyclique".
Contrairement à ce qui est autorisé en Java ou en Python, il est impossible d'écrire un programme ou un module contenant des dépendances cycliques en OCaml. Cette limitation est liée directement à la contrainte sur l'ordre de chargement et au fait qu'un module peut contenir du code qui sera exécuté lors de son chargement. Si un programme contenait deux modules codice_172 et codice_173 mutuellement dépendants, ni le code de l'un ni le code de l'autre ne pourrait s'exécuter lors du chargement, puisque le chargement de chacun des deux nécessiterait le chargement préalable de l'autre.
Il est généralement admis en informatique qu'une dépendance cyclique entre deux sous-ensembles distincts d'un programme est une erreur de conception. Il existe une plusieurs techniques pour transformer un projet contenant des dépendances cycliques de manière à supprimer ces dépendances. La technique la plus simple, qui n'est pas toujours envisageable ou satisfaisante, consiste à isoler les fonctionnalités qui introduisent ces dépendances cycliques et à les rassembler dans un module propre, puis à masquer cette technique d'implantation à l'aide de la signature appropriée.
Une autre technique consiste à introduire de la récursivité mutuelle entre modules à l'aide d'un opérateur de point fixe sur module. Nous verrons cette technique bien plus tard, lors du chapitre consacré aux usages avancés des modules.
Contraindre l'utilisation d'un module.
Dans ce qui précède, nous avons vu comment créer des modules. Si ceci est déjà très utile, OCaml propose quelques concepts supplémentaires qui serviront notamment à documenter l'utilisation d'un module ou à la restreindre l'utilisation d'un module. Cette notion de restriction sur l'utilisation a de nombreux usages. On s'en servira d'une part pour garantir au développeur du module que des changements dans le fonctionnement interne du module n'interfèreront pas avec le fonctionnement des programmes qui utilisent ce module, et d'autre part pour garantir que l'utilisateur ne peut pas, par accident, introduire des valeurs incorrectes ou contraires aux hypothèses, telles qu'un 25 d'Atout dans notre jeu de tarot.
Pour introduire ces restrictions, on emploiera des signatures (ou interfaces).
Signatures.
Un peu plus tôt dans ce chapitre, nous avons rencontré la signature du module codice_47. À l'aide de la directive codice_68, nous avions demandé à OCaml de nous afficher la liste des fonctions et types de ce module. Une signature n'est rien d'autre qu'une telle liste, sauvegardée dans un fichier d'extension codice_15 . Si nous désirons compiler une signature, ce qui n'est jamais nécessaire mais peut être utile le temps d'un test, il nous suffit de demander à OCamlBuild de produire un fichier d'extension codice_182
Ainsi, pour notre exemple du Tarot, nous allons commencer par écrire le fichier codice_183, qui accompagnera le fichier codice_148.
Note OCaml cherchera toujours la signature correspondant à un fichier codice_185 dans le fichier correspondant codice_186.
type t =
 | Trefle
 | Pique
 | Coeur
 | Carreau ;;
val to_string : t -> string ;;
De la même manière, nous pouvons écrire le fichier codice_187, qui accompagnera le fichier codice_188 :
type t =
 | Atout of int
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of int * Couleur.t ;;
val string_of_chiffre_francais : int -> string ;;
val to_string : t -> string ;;
val get_couleur : t -> t option ;;
À ce point, nous pourrions nous arrêter : nous disposons d'une interface complète pour chacun de nos deux modules, interface qui ne restreint rien. De fait, accordons-nous une petite pause, le temps de tester que tout ceci compile correctement :
$ ocamlfind batteries/ocamlbuild test.byte
Finished, 11 targets (3 cached) in 00:00:01.
Mais nous pouvons faire mieux. 
Commençons par cette fonction codice_189. Elle est certes nécessaire pour écrire la fonction codice_190 mais sa présence est plutôt embarrassante dans un module consacré à des cartes de tarot. Faisons-la donc simplement disparaître.
Voici donc une nouvelle version de codice_187 :
type t =
 | Atout of int
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of int * Couleur.t ;;
val to_string : t -> string ;;
val get_couleur : t -> Couleur.t option ;;
Comme nous pouvons le constater, cette fonction n'était effectivement pas nécessaire pour les utilisateurs du module codice_192 :
$ ocamlfind batteries/ocamlbuild test.byte
Finished, 11 targets (6 cached) in 00:00:01.
Maintenant que nous l'avons supprimée de l'interface du module, cette fonction ne peut plus être manipulée par les utilisateurs de ce module :
- : Carte.t -> string = <fun>
Error: unbound value Carte.string_of_chiffre_francais
Lorsque nous avons supprimé la fonction de l'interface du module, nous avons interdit à tout utilisateur de la manipuler. Par contre, cette fonction est toujours présente dans le module, car elle est nécessaire à codice_193, fonction dont nous venons de vérifier qu'elle est toujours présente.
Vocabulaire La possibilité de cacher certaines parties d'un module pour empêcher leur utilisation à l'extérieur du module est une forme limitée d'encapsulation.
Maintenant que nous avons légèrement nettoyé l'interface, intéressons-nous un peu plus au cas des Atouts : un Atout est une carte dont le numéro est compris entre 1 et 22 -- ou entre 0 et 21, le choix doit être fait lors de la conception du module. À l'heure actuelle, rien ne m'interdit d'introduire, par accident, une valeur codice_194. C'est malheureux et cela peut provoquer des bugs. Fort heureusement, nous pouvons corriger ce problème. 
Pour ce faire, nous allons commencer par modifier l'implantation pour faire en sorte que le constructeur codice_195 n'utilise pas des entiers mais des valeurs d'un nouveau type, que nous allons appeler codice_196 :
type t =
 | Atout of atout
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of int * Couleur.t ;;
Bien entendu, pour que ceci compile, il faut que nous ayons préalablement défini le type codice_196. Comme nous souhaitons que les valeurs d'atouts soient bien des entiers, nous allons faire de codice_196 un synonyme de codice_128 -- pour le moment.
type atout = int;;
Qu'avons-nous gagné pour le moment ? En apparence rien. En fait, l'intérêt de cette manipulation est que, si nous avons bien défini que le type codice_196 est un synonyme de codice_128, nous ne sommes pas obligés de rendre cette information publique. Ainsi, dans codice_187, nous allons écrire, sans plus de détails :
type atout;;
type t =
 | Atout of atout
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of int * Couleur.t ;;
Vocabulaire Un type dont le nom est donné dans l'interface mais dont la définition complète reste cachée est appelé un "type abstrait".
En rendant le type codice_196 abstrait, nous avons interdit à l'utilisateur de fabriquer lui-même des valeurs de ce type. Il nous reste à définir deux fonctions triviales qui permettront de transformer un codice_196 en codice_128, ce qui peut servir pour comparer des valeurs d'atouts, ou pour transformer un entier compris entre 1 et 22 en codice_196 :
let int_of_atout x = x;;
let atout_of_int x =
 if 1 <= x && x <= 22 then Some x
 else None;;
Quel type donner à ces fonctions ? La fonction codice_207 pourrait avoir pour type codice_208, tandis que codice_209 pourrait avoir pour type codice_210. Mais, comme nous avons en tête l'idée que la première de ces fonctions sert à transformer un codice_196 en nombre entier et que la deuxième de ces fonctions sert à transformer un nombre entier en codice_196, nous allons leur donner les types suivants :
val int_of_atout : atout -> int ;;
val atout_of_int : int -> atout option ;;
Nous disposons maintenant de deux fonctions qui permettent de manipuler les valeurs de type codice_196. Tant que nous n'ajoutons pas d'autre fonction qui renvoie une valeur de type codice_196, nous pouvons être certains que, <emph>par construction et malgré toute la mauvaise volonté du monde, aucun utilisateur n'arrivera à avoir un codice_196 dont la valeur n'est pas comprise entre 1 et 22</emph>.
Vocabulaire La valeur d'un élément de type codice_196 est dite "sure par construction", puis qu'il n'existe aucune manière pour un utilisateur de violer sa propriété fondamentale, qui est d'être comprise entre 1 et 22.
Note En programmation fonctionnelle, on considère comme primordial cet question de sûreté par construction. L'essentiel du temps passé à concevoir une bibliothèque de programmation fonctionnelle est généralement consacré à essayer d'obtenir le plus de garanties de sûreté.
Note Cette question de sûreté par construction est un très proche parent des "capacités par objets" présentes dans quelques langages de programmation conçus pour la sûreté (dont une variante de OCaml).
Vocabulaire Profitons-en pour préciser que, indépendamment du langage de programmation, une bibliothèque est dite "sûre" s'il n'y a pas moyen de causer de comportements imprévisibles à l'aide de cette bibliothèque. 
Vocabulaire Un langage est dit "sûr" s'il est possible de programmer des bibliothèques sûres. On considère généralement que le langage C et les langages dynamiques ne sont pas sûrs, puisqu'il y a moyen de détourner totalement le fonctionnement d'une bibliothèque pour lui faire adopter un comportement imprévisible. Java, C#, OCaml, Haskell ou Coq seront des langages beaucoup plus sûrs. Entre eux, on considère généralement Java comme moins sûr que C#, lui-même moins sûr que OCaml, lui-même moins sûr que Haskell, lui-même moins sûr que Coq.
Nous pouvons encore améliorer légèrement les choses en autorisant l'utilisateur à utiliser soit 0 soit 22 pour l'excuse (carte qui, rappelons-le, peut avoir deux valeurs distinctes) mais en nous débrouillant pouru que les valeurs employées en internes soient toujours comprises entre 1 et 22. Pour ce faire, il nous suffit de remplacer la définition de codice_209 par
let atout_of_int x =
 if x = 0 then Some 22
 else if 1 <= x && x <= 22 then Some x
 else None;;
De la même manière que nous avons introduit un type codice_196 et des fonctions codice_209 et codice_207 pour les atouts dont la valeur doit être comprise entre 1 et 22 (ou 0 et 22), nous allons introduire un type codice_221 et des fonctions codice_222 et codice_223 pour les cartes numérotées, dont la valeur doit être comprise entre 2 et 10.
type nombre;;
type t =
 | Atout of atout
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of nombre * Couleur.t ;;
val nombre_of_int : int -> nombre option ;;
val int_of_nombre : nombre -> int ;;
type nombre = int;;
type t =
 | Atout of atout
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of nombre * Couleur.t ;;
let int_of_nombre x = x;;
let nombre_of_int x =
 if 2 <= x && x <= 10 then Some x
 else None;;
Note Il n'est pas nécessaire de déclarer les types et valeurs dans le même ordre dans le fichier codice_14 et dans le fichier codice_15 correspondant.
Et voilà !
Documentation.
L'habitude veut que tous les fichiers codice_15 soient documentés à l'aide de commentaires écrits selon quelques conventions simples. Ces commentaires pourront plus tard être extraits et transformés en documentation HTML ou PDF. Pour le moment, contentons-nous de quelques annotations, que nous complèterons un peu plus tard lorsque nous aurons introduit les outils d'extraction.
Commençons par le fichier codice_183 :
(**
 Gestion des couleurs de cartes à jouer.
(**La couleur d'une carte.
 Chaque carte hormis les Atouts est doté d'une couleur de carte.*)
type t =
 | Trefle
 | Pique
 | Coeur
 | Carreau ;;
val to_string : t -> string ;;
Puis le fichier codice_187 :
type atout (**La valeur d'un atout. 
 Cette valeur peut être transformée en un entier compris entre 1 et 22. L'Excuse vaut donc 22*)
val int_of_atout : atout -> int
(**Renvoie le nombre correspondant à la valeur d'un Atout.
 Ce nombre est un entier compris entre 1 inclus et 22 inclus. L'Excuse vaut 22.*)
val atout_of_int : int -> atout option
(**Convertit un nombre en l'Atout correspondant.
 Le nombre doit être un entier compris entre 0 et 22 inclus. Les valeurs 0 et 22 représentent toutes deux l'Excuse.
 Cette fonction renvoie None si le nombre n'est pas compris entre 0 et 22.*)
type nombre (**La valeur d'une carte numérotée.
 Cette valeur peut être transformée en un entier compris entre 2 et 10. L'As est géré comme une figure.*)
val int_of_nombre : nombre -> int
(**Renvoie le nombre correspondant à la valeur d'une carte numérotée.
 Ce nombre est un entier compris entre 2 inclus et 10 inclus.*)
val nombre_of_int : int -> nombre option
(**Convertit un entier en la valeur de la carte numérotée correspondante.
 Le nombre doit être un entier compris entre 2 et 10 inclus.
 Cette fonction renvoie None si le nombre n'est pas compris entre 2 et 10 inclus.*)
type t =
 | Atout of atout
 | Roi of Couleur.t
 | Dame of Couleur.t
 | Cavalier of Couleur.t
 | Valet of Couleur.t
 | As of Couleur.t
 | Nombre of nombre * Couleur.t (**Une carte numérotée (l'As est considéré comme une figure)*);;
val to_string : t -> string ;;
val get_couleur : t -> Couleur.t option ;;
Types privés.
Nous venons de voir la notion de types abstraits, qui nous a permis de garantir que les cartes numérotées et les atouts étaient forcément des valeurs valides. Le mécanisme consiste à masquer toute information sur la manière dont est implanté un type, et à ne fournir que des fonctions sûres pour la manipulation des valeurs de ce type.
Dans certains cas, cela semble un peu exagéré. On pourrait préférer laisser publique l'information sur la manière dont est implanté un type et de se contenter de restreindre la possibilité de "créer" des valeurs de ce type. Ainsi, dans notre exemple en cours, la seule manière d'utiliser une valeur de type codice_196 ou codice_221 est de commencer par la convertir en entier à l'aide de codice_207 ou codice_223. Nous gagnerions en simplicité (et peut-être en vitesse) si nous pouvions autoriser un programmeur à utiliser toute valeur de type codice_221 ou codice_196 directement comme un codice_128, à condition de disposer d'un mécanisme pour protéger la création des valeurs de type codice_221 ou codice_196.
C'est le principe des "types privés".
Ainsi, dans codice_187, nous pouvons remplacer
type atout (**La valeur d'un atout.
 Cette valeur peut être transformée en un entier compris entre 1 et 22. L'Excuse vaut donc 22*)
val int_of_atout : atout -> int
(**Renvoie le nombre correspondant à la valeur d'un Atout.
 Ce nombre est un entier compris entre 1 inclus et 22 inclus. L'Excuse vaut 22.*)
type nombre (**La valeur d'une carte numérotée.
 Cette valeur peut être transformée en un entier compris entre 2 et 10.
 L'As n'est pas représenté comme un nombre mais comme une figure.*)
val int_of_nombre : nombre -> int
(**Renvoie le nombre correspondant à la valeur d'une carte numérotée.
 Ce nombre est un entier compris entre 2 inclus et 10 inclus.*)
par 
type atout = private int
 (**La valeur d'un atout.
 Il s'agit d'un entier compris entre 1 et 22. L'Excuse vaut donc 22.*)
type nombre = private int
 (**La valeur d'une carte numérotée.
 Il s'agit d'un entier compris entre 2 et 10.
 L'As n'est pas représenté comme un nombre mais comme une figure.*)
Dans le fichier codice_188, nous pouvons de même nous débarrasser de codice_223 et codice_207, devenus inutiles.
Pour nous convaincre que ceci fonctionne correctement, commençons par compiler le tout :
$ ocamlfind batteries/ocamlbuild carte.cmo couleur.cmo
Finished, 6 targets (2 cached) in 00:00:00.
$ ocamlfind batteries/ocaml
Puis chargeons et testons dans le mode interactif :
- : Carte.atout option = Some 5
- : Carte.atout option = None
Error: This expression has type int but is here used with type
 Carte.atout = Carte.atout
Et voilà.
À titre de comparaison.
Types abstraits.
La notion de type abstrait apparait, sous une forme légèrement différente, dans la majorité des langages de programmation orientée objet statiquement typés.
Ainsi, nous pouvons considérer qu'un type abstrait est un type tel que :
Dans un langage à objets, ces caractéristiques se traduiront par :
En Java, pour implanter la première version de notre type codice_221, nous écrirons donc :
//Fichier Nombre.java
public interface Nombre
 public int getInt();
//Fichier NombreImpl.java
protected class NombreImpl implements Nombre
 private final int valeur;
 protected NombreImpl(int valeur)
 this.valeur = valeur;
 public int getInt()
 return this.valeur;
En plus de ceci, de la même manière que nous avons fourni une fonction codice_222 en OCaml, nous devons fournir une fonction ou une méthode "usine" en Java, telle que :
//Fichier NombreFactory.java
public class NombreFactory
 public static Nombre makeNombre(int valeur) throws Exception
 if(2 <= valeur && valeur <= 10)
 return new NombreImpl(valeur);
 else
 throw new Exception();
La version Java est plus verbeuse et prête plus à confusion. En contrepartie, elle permet plus d'extensibilité et plus de modularité, puisqu'une implantation de codice_244 peut être remplacée par une autre sans avoir à réécrire le code qui manipule les instances type codice_244.
Types privés.
De la même manière que les types abstraits, les types privés apparaissent, sous une autre forme, dans les langages orientés objet.
Ainsi, nous pouvons considérer qu'un type privé est un type tel que :
Dans un langage à objets, ces caractéristiques se traduiront naturelle par une classe dotée d'un constructeur protégé. Ainsi, en Java, pour implanter la deuxième version de notre type nombre, nous écrirons
//Fichier Nombre.java
public class Nombre
 private final int valeur;
 protected Nombre(int valeur) throws Exception
 if(2 <= valeur && valeur <= 10)
 this.valeur = valeur;
 else
 throw new Exception();