Cours 6

Htmligure
Version & licenses
Creative Commons License

Étendre des modules

Guyslain
Thursday, 21 July 2016

Retour au sommaire.

Étendre un module.

Un des cas d'usage des modules est de pouvoir définir une implémentation minimaliste d'une interface, puis de l'appliquer à un foncteur pour dériver automatiquement de nombreuses fonctions.

Les files de priorités.

Continuons sur l'exemple des files de priorités. On pourrait à partir de cette file de priorité définir automatiquement des fonctions pour créer une file de priorité depuis une liste, ou extraire une liste triée depuis une file de priorité. Cela correspondrait à enrichir l'interface des modules de priorité pour obtenir ceci :

  1. module type Heap = sig
  2. include MinimalHeap
  3. val of_list : key list -> t
  4. val to_list : t -> key list
  5. val sort : key list -> key list
  6. end

On note ici l'utilisation du mot-clé include qui permet d'inclure dans une interface le contenu d'une autre interface. En plus des définitions de la file de priorité minimale, nous trouvons donc les fonctions de conversions de/vers les listes et une fonction de tri.

Il n'est alors pas nécessaire de redéfinir entièrement le module de files de priorité : on peut réutiliser l'implémentation des tas gauches, à l'aide d'un foncteur.

  1. module Extend :
  2. functor (Heap : MinimalHeap) ->
  3. Heap with type t = Heap.t and type key = Heap.key
  4. = functor (Heap : MinimalHeap) ->
  5. struct
  6. include Heap

  7. let of_list list =
  8. List.fold_left (fun heap elt -> insert elt heap) empty list

  9. let to_list heap =
  10. let rec accumulate sorted heap =
  11. match observe heap with
  12. | None -> List.rec sorted
  13. | Some (top,popped) -> accumulate (top :: sorted) popped
  14. in
  15. accumulate [] heap

  16. let sort list = list |> of_list |> to_list
  17. end

À nouveau, dans l'implémentation, include permet de récupérer toutes les définitions d'un module. On peut noter qu'il faut correctement exporter les deux types t et key. Dans ce foncteur, l'argument Heap peut être n'importe quel module implémentant l'interface MinimalHeap, ce qui a deux conséquences :

  • ce n'est pas nécessairement l'implémentation par tas gauche, on n'a donc pas accès à la structure privée du module Heap.
  • ce n'est pas nécessairement l'implémentation par tas gauche, donc n'importe quelle implémentation minimale de files de priorité peut être étendue par le foncteur Extend. Nous avons codé une fois pour toute les fonctions d'Extend.

Un exemple d'utilisation de cette technique est la librairie Ocamlgraph d'algorithmes sur les graphes. Tous les algorithmes sont écrits dans des foncteurs, prenant un module d'interface Graph (en fait, il y a même plusieurs interfaces possibles pour les graphes). Chaque algorithme est donc implémenté une fois, définitivement, et peut être ensuite utilisé avec n'importe quelle implémentation de graphes, ce qui permet d'utiliser des représentations très haut-niveau de graphes, selon les besoins de l'utilisateur de la librairie. Des implémentations par défaut sont aussi proposées.

Les monades.

Étudions un autre exemple. Rappelons l'interface des monades :

  1. module type Monad = sig
  2. type 'a t
  3. val return : 'value -> 'value t
  4. val (>>=) : 'value t -> ('value -> 'result t) -> 'result t
  5. end

L'un des intérêts des monades est de modéliser le séquencement d'actions : faire une chose, puis une autre, puis une autre. Nous avons vu à travers de nombreux exemples que le (>>=) s'utilise de la même façon que le pipe (|>), mais avec des fonctions produisant des résultats dans le type paramétré de la monade.

Une fois les deux opérations return et (>>=) définies, il est possible d'en définir plein d'autres automatiquement. On peut donc définir un foncteur (avec une petite sélection de fonctions possibles) :

  1. module type ExtMonad = sig
  2. include Monad

  3. (** Monads are functors *)
  4. val map : 'value t -> ('value -> 'result) -> 'result t

  5. (** [join] = [flatten] = [concat] *)
  6. val join : 'value t t -> 'value t

  7. (** applies actions of a sequence and return the sequence of results *)
  8. val sequence : 'result t list -> 'result list t

  9. (** [fold] when the action on each element is monadic *)
  10. val foldM : ('state -> 'elt -> 'state t) -> 'state -> 'elt list -> 'state t

  11. (** Lifts an ordinary function to a "monadic" function. *)
  12. val lift : ('arg -> 'result) -> ('arg -> 'result t)
  13. end

  14. module ExtendMonad :
  15. functor (M : Monad) -> ExtMonad with type 'a t = 'a Monad.t
  16. = functor (M : Monad) ->
  17. struct
  18. include M

  19. let map fct ma = ma >>= fun a -> return (fct a)

  20. let join mma = mma >>= fun ma -> ma

  21. let sequence actions =
  22. let rec accumulate results = function
  23. | [] -> return (List.rev results)
  24. | first :: actions ->
  25. first >>= fun first_res ->
  26. accumulate (first_res :: results) actions
  27. in
  28. accumulate [] actions

  29. let foldM fct init args =
  30. List.fold_left
  31. (fun ma elt -> ma >>= fun a -> fct a elt)
  32. (return init)
  33. args

  34. let lift fct arg = return (fct arg)
  35. end

Retour au sommaire.