Un type paramétré 'elt t est un conteneur si les valeurs de ce type servent à représenter des ensembles de valeurs de type 'elt. Le paramètre du type sert donc à préciser le types des valeurs contenues. Ce sont en général les structures de données. Les conteneurs les plus basiques en OCaml sont les listes, nous avons donc des listes d'entiers int list, de listes de caractères char list, des listes de listes de booléens bool list list, etc. Dans QuickCheck, le type Iter.t est un type conteneur. La librairie standard contient plusieurs autres types conteneurs, par exemple : les ensembles (module Set), les files (module Queue), les piles (module Stack), les dictionnaires (module Map), dont certains sont des types paramétrés.
Le principe d'un conteneur est de mettre des choses dedans, puis de pouvoir les en sortir plus tard. Cela permet de grouper des valeurs ensemble et de les manipuler de façon unifiée.
Nous ferons un cours spécifique sur la manipulation des listes, nous n'introduisons ici que les bases. Une liste représente une séquence d'éléments de même type, manipulable par une seule de ses extrémités (que nous appelons par convention le début de la liste). Les fonctions de listes sont définies dans le module List de la librairie standard.
Une liste peut-être vide, on la note []. La liste vide est une valeur polymorphe : elle a le type elt list pour tout type elt, donc [] : 'elt list. On peut tester si une liste est vide en testant l'égalité avec [].
Si une liste n'est pas vide, elle se décompose en un élément de tête (l'élément au début de la liste), et une queue (la liste de tous ses éléments, sauf la tête). On peut récupérer l'élément de tête avec la fonction List.hd : 'elt list -> 'elt, et la queue avec List.tl : 'elt list -> 'elt list.
Ces deux fonctions provoquent une erreur à l'exécution si la liste est vide, donc :
Pour construire un liste, on peut le faire explicitement, en utilisant la notation [elt1;elt2;...;eltn]. Ou bien on peut le faire en ajoutant un élément à une liste déjà existante grâce à l'opérateur (::) : 'elt -> 'elt list -> 'elt list. L'insertion se fait en début de liste, il n'est pas possible d'insérer un élément en fin de liste.
Le type Iter.t est utilisé par QuickCheck pour stocker l'ensemble des simplifications possibles d'une valeur. Par exemple dans le module de fraction, nous avons défini :
Cette fonction dit que les simplifications de $\frac{n}{d}$ possibles sont $\frac{n/2}{d}$ (mais seulement si $n \neq 0$) et $\frac{n}{(d+1)/2}$ (mais seulement si $d > 1$). Autrement dit, si $\frac{n}{d}$ est un contre-exemple, on lui demande d'essayer si l'une des deux autres fractions ne serait pas aussi un contre-exemple.
Le résultat de l'appel à shrink_ratio r est donc une ensemble (peut-être vide) de fractions plus simples que r.
Nous avons utilisé plusieurs valeurs de Iter :
D'autres fonctions sont données. Par exemple, puisque les listes sont des conteneurs, il est naturel de penser qu'on peut convertir une liste en un conteneur de type Iter.t. C'est ce que fait la fonction of_list. La plupart des conteneurs donnent des fonctions de conversion depuis ou vers les listes.
Voici un exemple alambiqué de création d'un ensemble d'entiers.
On vérifie le contenu de l'ensemble :
Puisque Iter.t est un type paramétré, on peut faire des conteneurs contenant n'importe quel type de valeurs (du moment que toutes les valeurs du conteneur sont du même type). Nous pouvons faire des conteneurs d'entiers, des conteneurs de flottants, etc. Une conséquence est que si on dispose d'un conteneur d'entiers, et d'une fonction des entiers vers les flottants, en appliquant la fonction à tous les éléments du conteneur d'entier nous devrions pouvoir obtenir un conteneur de flottant. Effectivement, c'est ce que fait la fonction map : 'elt t -> ('elt -> 'image) -> 'image t. À nouveau, la plupart des conteneurs possède une fonction map (en particulier les listes). Convertissons notre conteneur d'entier en conteneur de flottant :
Puis affichons le contenu :
On peut aussi simplement faire une manipulation sur les entiers du premier conteneur :
Nous avons dit plus tôt que les conteneurs contiennent des valeurs que l'on peut ensuite récupérer. Iter.t est un type de conteneur très simple, et il n'y a pas de fonctions de récupération dans le module Iter. La façon de récupérer les valeurs du conteneur est cachée dans la définition du type Iter :
La seule façon d'utiliser les éléments de container : elt t est d'appliquer container (qui est en fait une fonction) à une fonction utilisant ses éléments en argument. C'est ce que nous avons fait plus haut pour la fonction print_all. Cela peut sembler étrange, mais ce n'est pas étonnant lorsqu'on sait qu'en programmation fonctionnelle, tout peut être représenté uniquement par des fonctions.
Iter ne propose pas de fonction plus élaborée pour accéder au contenu d'un conteneur. Pourquoi ? car ce type sert à l'utilisateur de QuickCheck à mettre des valeurs à disposition de l'algorithme de test. L'utilisateur n'est pas censé récupérer les valeurs de ce conteneur.