Le prochain exemple concerne la conversion de valeurs en chaînes de caractères. Dans QCheck, le module Print définit des combinateurs pour écrire facilement des fonctions to_string. Voyons comment construire un tel module.
Le type principal est celui des fonctions de conversion. Il est bien sûr paramétré par le type de ce qui est converti : c'est donc un consommateur. On le définit très simplement, comme une fonction vers string :
On peut alors définir des convertisseurs pour les types de bases :
Puis on ajoute des combinateurs pour pouvoir construire des convertisseurs sur des types plus complexes :
On peut ainsi obtenir facilement un fonction de conversion pour des types assez simples :
Cela reste relativement limité. Si on reprend l'exemple des personnes, pour obtenir un formattage contenant le nom et l'âge d'une personne, on ne peut pas utiliser directement les combinateurs pour écrire une fonction de conversion, notamment parce que nous n'avons rien pour ajouter des chaînes de caractères brutes, ni pour imprimer plusieurs choses. Ajoutons donc un combinateur constant (il construit toujours la même chaîne de caractères), et un opérateur de concaténation :
On peut maintenant écrire :
On arrive a peu près au niveau de ce que permet de faire sprintf, mais en plus modulaire puisque nous avons la possibilité de créer nos propres formattages pour n'importe quel type; nous ne sommes pas limité aux types de bases.
On va ajouter un dernier combinateur. Imaginons que chaque personne possède une liste d'enfants, et qu'on souhaite aussi afficher la descendance de chaque personne qu'on affiche (heureusement, cette relation de filiation est acyclique, donc on n'affiche ainsi qu'un nombre fini de personnes). On voudrait écrire :
Facile, mais faux. En effet, on ne peut pas définir de telles récursions : tout appel récursif doit se faire soit dans une abstraction (dans le corps d'une fonction), soit dans un constructeur. Pour pouvoir faire cela, il nous faut un combinateur en plus : le combinateur de point-fixe, qui permet d'exprimer des récursions.
On a alors :
L'exemple que nous venons de voir, bien qu'il soit déjà relativement puissant, manque encore un peu de fonctionnalité. Par exemple, on ne peut pas gérer l'indentation convenablement, ni faire de la justification de texte.
Par ailleurs, ces combinateurs ne sont pas très efficaces, en particulier parce ce qu'ils manipulent les chaînes en faisant des concaténations dès que possible. Il crée donc beaucoup de chaînes intermédiaires et cela a un coût. Il serait plus intéressant de représenter les convertisseurs comme convertissant vers une structure intermédiaire, typiquement un arbre de chaînes de caractères, puis d'avoir une fonction convertissant ces arbres en une seule chaîne de caractères. Plus généralement, si on veut écrire cette chaîne dans un fichier ou en sortie, ce n'est pas la peine de la construire. Ainsi, passer par une représentation intermédiaire permettrait d'à la fois pouvoir gérer des sorties variées efficacement, et de permettre du formattage de texte avec indentation, justification et alignement des textes.
En fait c'est exactement ce que permet de faire le module Format de la librairie standard. Au premier abord, cela ressemble au module Printf.
Mais Format permet aussi de créer des boîtes, avec "@[" et "@]", pour grouper des éléments que Format essaie autant que possible de présenter ensemble (sur une même ligne). Le motif "@ " permet d'insérer un espacement pouvant être utilisé comme retour à la ligne par l'algorithme de formattage, et "@;" indique la possibilité d'ajouter un retour à la ligne. Par ailleurs, on peut formatter des expressions récursivement avec le motif "%a" qui demande un argument supplémentaire : la fonction de formattage à utiliser en plus de l'argument à formatter.
Pour l'exemple des personnes, cela donne :
<hv 2> indique quel type de formattage est attendu pour le texte entre le "@[" juste devant et le "@]"correspondant. On obtient :
Bien sûr, les nombreuses possibilités de Format exigent une quantité de subtilités supérieure à notre petit prototype. La documentation de Format contient un tutoriel expliquant les différents concepts et donnant quelques exemples.