On vient de regarder des modules plutôt cool, mais comment crée-t-on nos propres modules ? Presque tous les langages de programmation vous permettent de découper votre code en plusieurs fichiers et Haskell également. Lorsqu’on programme, il est de bonne pratique de prendre des fonctions et des types qui partagent un but similaire et de les placer dans un module. Ainsi, vous pouvez facilement réutiliser ces fonctions plus tard dans d’autres programmes juste en important le module.
Voyons comment faire nos propres modules en créant un petit module fournissant des fonctions de calcul de volume et d’aire d’objets géométriques. Commençons par créer un fichier Geometry.hs. On dit qu’un module exporte des fonctions. Cela signifie que quand j’importe un module, je peux utiliser les fonctions que celui-ci exporte. Il peut définir des fonctions que ses propres fonctions appellent en interne, mais on peut seulement voir celles qu’il a exportées.
Au début d’un module, on spécifie le nom du module. On a créé un fichier Geometry.hs, nous devrions donc nommer notre module Geometry. Puis, nous spécifions les fonctions qu’il exporte, et après cela, on peut commencer à écrire nos fonctions. Démarrons.
module Geometry ( sphereVolume , sphereArea , cubeVolume , cubeArea , cuboidArea , cuboidVolume ) where
Comme vous pouvez le voir, nous allons faire des aires et des volumes de sphères, de cubes et de pavés droits. Définissons nos fonctions :
module Geometry ( sphereVolume , sphereArea , cubeVolume , cubeArea , cuboidArea , cuboidVolume ) where
sphereVolume :: Float -> Float
sphereVolume radius = (4.0 / 3.0) * pi * (radius ^ 3)
sphereArea :: Float -> Float
sphereArea radius = 4 * pi * (radius ^ 2)
cubeVolume :: Float -> Float
cubeVolume side = cuboidVolume side side side
cubeArea :: Float -> Float
cubeArea side = cuboidArea side side side
cuboidVolume :: Float -> Float -> Float -> Float
cuboidVolume a b c = rectangleArea a b * c
cuboidArea :: Float -> Float -> Float -> Float
cuboidArea a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2
rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b
De la géométrie élémentaire. Quelques choses à noter tout de même. Puisqu’un cube est un cas spécial de pavé droit, on a défini son aire et son volume comme ceux d’un pavé dont les côtés ont tous la même longueur. On a également défini la fonction auxiliaire rectangleArea, qui calcule l’aire d’un rectangle à partir des longueurs de ses côtés. C’est plutôt trivial puisqu’il s’agit d’une simple multiplication. Remarquez comme on l’utilise dans nos fonctions de ce module (dans cuboidArea et cuboidVolume ), mais on ne l’exporte pas ! On souhaite que notre module présente des fonctions de calcul sur des objets en trois dimensions, donc on n’exporte pas rectangleArea.
Quand on crée un module, on n’exporte en général que les fonctions qui agissent en rapport avec l’interface de notre module, de manière à cacher
l’implémentation. Si quelqu’un utilise notre module Geometry, il n’a pas à se soucier des fonctions que l’on n’a pas exportées. On peut décider de changer ces fonctions complètement ou de les effacer dans une nouvelle version (on pourrait supprimer rectangleArea et utiliser * à la place) et personne ne s’en souciera parce qu’on ne les avait pas exportées.
Pour utiliser notre module, on fait juste :
import Geometry
Geometry.hs doit tout de même être dans le même dossier que le programme qui souhaite l’importer.
Les modules peuvent aussi être organisés hiérarchiquement. Chaque module peut avoir un nombre de sous-modules et eux-mêmes peuvent avoir leurs sous- modules. Découpons ces fonctions de manière à ce que Geometry soit un module avec trois sous-modules, un pour chaque type d’objet.
D’abord, créons un dossier Geometry. Attention à la majuscule à G. Dans ce dossier, placez trois dossiers : Sphere.hs, Cuboid.hs et Cube.hs (NDT : “Cuboid” signifie pavé droit). Voici ce que les fichiers contiennent :
Sphere.hs
module Geometry.Sphere ( volume
, area ) where
volume :: Float -> Float
volume radius = (4.0 / 3.0) * pi * (radius ^ 3)
area :: Float -> Float
area radius = 4 * pi * (radius ^ 2)
Cuboid.hs
module Geometry.Cuboid ( volume
, area ) where
volume :: Float -> Float -> Float -> Float
volume a b c = rectangleArea a b * c
area :: Float -> Float -> Float -> Float
area a b c = rectangleArea a b * 2 + rectangleArea a c * 2 + rectangleArea c b * 2
rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b Cube.hs module Geometry.Cube ( volume , area ) where
import qualified Geometry.Cuboid as Cuboid
volume :: Float -> Float
volume side = Cuboid.volume side side side
area :: Float -> Float
area side = Cuboid.area side side side
Parfait ! Tout d’abord, nous avons Geometry.Sphere. Remarquez comme on l’a placé dans le dossier Geometry puis nommé Geometry.Sphere. Idem pour le pavé. Remarquez aussi comme dans chaque sous-module, nous avons défini des fonctions avec le même nom. On peut le faire car les modules sont séparés. On veut utiliser des fonctions de Geometry.Cuboid dans Geometry.Cube, mais on ne peut pas simplement import Geometry.Cuboid parce que ce module exporte des fonctions ayant le même nom que celles de Geometry.Cube. C’est pourquoi l’import est qualifié, et tout va bien.
Donc maintenant, si l’on se trouve dans un fichier qui se trouve au même niveau que le dossier Geometry, on peut par exemple :
import Geometry.Sphere
Et maintenant, on peut utiliser area et volume, qui nous donneront l’aire et le volume d’une sphère. Et si l’on souhaite jongler avec deux ou plus de ces modules, on doit utiliser des imports qualifiés car ils exportent des fonctions avec des noms identiques. Tout simplement :
import qualified Geometry.Cuboid as Cuboid
import qualified Geometry.Cube as Cube
Et maintenant, on peut appeler Sphere.area, Sphere.volume, Cuboid.area, etc. et chacune calculera l’aire ou le volume de l’objet correspondant.
La prochaine fois que vous vous retrouvez en train d’écrire un fichier très gros avec plein de fonctions, essayez de voir lesquelles partagent un but commun et si vous pouvez les regrouper dans un module. Vous n’aurez plus qu’à importer ce module si vous souhaitez réutiliser ces fonctionnalités dans un autre programme.