Cours 2

Htmligure
Version & licenses
Creative Commons License

Les consommateurs

Guyslain
Thursday, 21 July 2016

Retour au sommaire.

Les consommateurs

Les consommateurs sont des types paramétrés 'consumed t qui se comporte comme des fonctions prenant des valeurs de type 'consumed en argument. Tout l'intérêt est d'obtenir des résultats, à chaque application ou bien de façon différée, donc les consommateurs permettent de récupérer des valeurs dépendant des valeurs consommées. Le type des valeurs consommés est donc le paramètre du type paramétrés du consommateur.

QCheck.Print

Un premier exemple est le type 'value QCheck.Print.t qui n'est qu'un renommage du type 'value -> string. Ce sont donc des fonctions consommant des valeurs selon leur type paramétré, et fournissant des chaînes de caractères.

  1. let int_printer = QCheck.Print.int

  2. # int_printer 42;;
  3. - : string = "42"
  4. # int_printer 28;;
  5. - : string = "28"

Une fois de plus QCheck.Print définit des combinateurs, afin de créer des fonctions de conversions plus intéressantes :

  1. # QCheck.Print.(list (pair int float)) [ (1, 1.0); (2, 1.41); (3, 1.73) ];;
  2. - : string = "[(1, 1.); (2, 1.41); (3, 1.73)]"
  3. # QCheck.Print.(option (list int)) (Some [3;2;4;1;5]);;
  4. - : string = "Some ([3; 2; 4; 1; 5])"

Par ailleurs, si on sait convertir un entier en chaîne de caractères, et qu'on sait obtenir une entier depuis une fraction, on sait convertir une fraction en chaîne de caractères : il suffit d'obtenir un entier depuis la fraction et de convertir cet entier. Nous avons déjà vu ce mécanisme, avec les fonctions map dans QCheck.Iter et dans QCheck.Gen ! Sauf que si on y fait attention, cette fois-ci les types sont dans l'autre sens :

  1. (* Iter and Gen : *)
  2. val map : ('elt -> 'im) -> 'elt t -> 'im t

  3. (* For Print we need : *)
  4. val comap : ('any -> 'printable) -> 'printable t -> 'any t

D'une certaine façon, les producteurs et les consommateurs fonctionnent en miroir les uns des autres, ce n'est donc pas étonnant que les types des fonctions map et comap soient aussi miroir. En tout cas nous pouvons maintenant écrire un fonction pour afficher le dénominateur d'une fraction :

  1. let get_denum (n,d) = d

  2. let denum_print : Ratio.t QCheck.Print.t =
  3. let open QCheck.Print in
  4. comap get_denum
  5. @@ int

ou pour afficher le premier dénominateur d'une liste non-vide de fractions :

  1. let first_denum_in_list : Ratio.t list QCheck.Print.t =
  2. let open QCheck.Print in
  3. comap List.hd
  4. @@ comap get_denum
  5. @@ int

Prédicats.

Il existe de nombreux exemples de consommateurs. En voici un autre pour encoder des prédicats, c'est-à-dire des fonctions produisants des booléens.

  1. module Predicate :
  2. sig
  3. type 'value t
  4. val test : 'value t -> 'value -> bool
  5. val always : 'value t (* always true *)
  6. val never : 'value t (* always false *)
  7. val negate : 'value t -> 'value t
  8. val is : 'value -> 'value t
  9. val checks : ('value -> bool) -> 'value t
  10. val or_else : 'value t -> 'value t -> 'value t
  11. val and_also : 'value t -> 'value t -> 'value t
  12. val all : 'value t -> 'value list t
  13. val exists : 'value t -> 'value list t
  14. val both : 'left t -> 'right t -> ('left * 'right) t
  15. val either : 'left t -> 'right t -> ('left * 'right) t
  16. val comap : ('value -> 'testable) -> 'testable t -> 'value t
  17. end

Une valeur de type value Predicate.t est essentiellement une fonction du type value vers le type bool. On peut d'ailleurs créer un Predicate.t à partir d'une fonction value -> bool en utilisant la fonction Predicate.checks. Et on peut utiliser un prédicat pour tester une valeur avec Predicate.test.

Voyons quelques exemples :

  1. let is_even : int Predicate.t = Predicate.checks (fun n -> n mod 2 = 0)
  2. let are_all_elements_even : int list Predicate.t = Predicate.all is_even


  3. # Predicate.test are_all_elements_even [2;6;4;8];;
  4. - : bool = true
  5. # Predicate.test are_all_elements_even [4;6;1;10];;
  6. - : bool = false

is_even est un prédicat sur les entiers, décidant la parité. En utilisant le combinateur Predicate.all, on construit un prédicat sur les listes d'entiers, qui décide si tous les entiers d'une liste sont pairs.

  1. let are_all_ratios_integral : Ratio.t list Predicate.t =
  2. let open Predicate in
  3. all
  4. @@ comap get_denum
  5. @@ is 1

Tout comme Print, Predicate propose un combinateur comap. Le prédicat se lit "tous les dénominateurs sont égaux à 1" , il décide donc si toutes les fractions d'une liste sont entières. Notons l'usage de @@ qui permet d'écrire le code dans le sens de lecture naturel, alors que dans les producteurs on utiliser l'application inversée |>. C'est de nouveau l'effet miroir entre producteurs et consommateurs.

On termine avec un prédicat pour tester si une liste d'associations, c'est-à-dire de paires (clé, valeur), possède une certaine clé. On peut utiliser comap pour récupérer la clé d'une paire, ou bien faire un prédicat sur les paires avec both.

  1. let contains key : ('key * 'value) list Predicate.t =
  2. let open Predicate in
  3. exists
  4. @@ comap fst
  5. @@ is key

  6. (* Equivalently: *)
  7. let contains' key : ('key * 'value) list Predicate.t =
  8. let open Predicate in
  9. exists @@ both (is key) always

  10. # Predicate.test (contains "foo") [("bar",12); ("foo",42)];;
  11. - : bool = true
  12. # Predicate.test (contains "foo") [("bar",12); ("baz",42)];;
  13. - : bool = false

Retour au sommaire.