janvier
2010
Le billet d’aujourd’hui a pour but de faire toute la lumière sur la qualification à l’intérieur des modules Objective-Caml.
Un module paramétré est un module-fonction, c’est-à-dire une fonction d’un module-type vers un module-type qui accepte un module-valeur pour argument réel.
La définition d’un module paramétré conduit fréquemment à ce genre de code où Graphics est l’argument formel du module-fonction Draw.
module type Graphics = sig type color = Black | White type point = {x:int;y:int} val draw_line : int -> int -> int -> int -> unit end module Draw (G: Graphics) = struct let pen = Black let background = White let connect a b = G.draw_line a.x a.y b.x b.y end
Au premier abord il est surprenant qu’OCaml n’accepte pas ce code.
Il ne l’accepte pas pour deux raisons similaires :
- les couleurs Black et White ne sont pas définies dans le module Draw
- les champs x et y ne sont pas définis dans le module Draw
Le contournement évident consiste à tout qualifier par le module G.
module Draw (G: Graphics) = struct let pen = G.Black let background = G.White let connect a b = G.draw_line a.G.x a.G.y b.G.x b.G.y end
Une fois tout qualifié par le module G ocaml accepte le code.
Mais cette solution peut vite devenir assez lourde.
Il y a d’autres moyens pour soulager la notation.
Un moyen classique consiste à ouvrir le module G de façon à accéder à ses définitions sans avoir à qualifier systématiquement.
module Draw (G: Graphics) = struct open G let pen = Black let background = White let connect a b = draw_line a.x a.y b.x b.y end
Toutefois un reproche que l’on peut faire à l’ouverture de module c’est qu’on ouvre tout ou rien. Il n’est pas possible, à l’aide d’open, d’ouvrir le type G.point parce que les champs G.x et G.y impactent beaucoup la lisibilité mais de laisser le type G.color fermé parce que G.Black et G.White ne sont pas très gênants.
Ça n’est pas possible à l’aide d’open mais ça l’est à l’aide d’une égalité de types.
L’idée c’est de déclarer un type point équivalent au type G.point.
Ce type local agira comme un alias pour le type G.point.
De cette façon le module G reste fermé mais on peut accéder directement aux composantes x et y sans les qualifier.
module Draw (G: Graphics) = struct type point = G.point = {x:int;y:int} let pen = G.Black let background = G.White let connect a b = G.draw_line a.x a.y b.x b.y end
En allant jusqu’au bout de cette logique on peut carrément inclure le module G, dans ce cas le module Draw contient une copie complète du module G et il n’y a plus rien à qualifier.
module Draw (G: Graphics) = struct include G let pen = Black let background = White let connect a b = draw_line a.x a.y b.x b.y end
Attention toutefois à ne pas abuser du include là où un open ferait très bien l’affaire.
Le include ajoute tout le module à l’interface du module en cours.
Il a donc un effet à l’extérieur du module alors que l’effet de open est limité à l’intérieur.
N’utilisez include que si c’est nécessaire pour implanter l’interface voulue, si vous n’avez que besoin d’alléger la qualification dans l’implantation alors c’est open qu’il vous faut.