Il existe deux façons de définir de nouvelles représentations, la première correspond à faire le produit de deux types, au sens mathématique du produit Cartésien. C'est aussi une construction qui existe dans les langages impératifs : construire une donnée qui en contient plusieurs autres, en quantité fixée. Le nom habituel pour ces constructions est enregistement (en anglais record), mais nous verrons rapidement que nous avons de bonnes raisons de les appeler types produits.
Un exemple de type produit est donné par le type des nombres complexes, dans le module Complex de la librairie standard. Un complexe est défini par deux valeurs réels, la partie réelle et la partie imaginaire. N'importe quelle paire de deux réels définit un nombre complexe, et inversement tout nombre complexe se décrit simplement à l'aide de deux réels. Il s'agit donc d'une bonne représentation (à ceci près que nous utilisons des flottants pour représenter les réels, ce qui n'est pas toujours une bonne représentation, mais cela suffit le plus souvent).
Les types produits se construisent entre deux accolades, par une liste de champs. Chaque champ est déterminé par un identifiant, qui doit commencer par une minuscule, et à chaque champ est associé une information, une valeur. Les champs sont séparés par des point-virgules, l'ordre des champs n'a pas d'importance.
Lors de la définition d'un type produit, on doit indiquer pour chaque champ le type des informations associés au champ, en utilisant le caractère :. Lors de la construction d'un champ, à chaque champ est associé une valeur, en utilisant le caractère =.
Nous pouvons donc maintenant définir un nombre complexe :
et la syntaxe de construction est la même que la syntaxe d'observation :
Dans cette fonction de rotation, x est l'abstraction de la partie réelle et y l'abstraction de la partie imaginaire de l'argument.
Ce n'est pas toujours pratique d'utiliser les observations par motifs lorsqu'on manipule des enregistrements. Du coup il existe plein de possibilités syntaxiques pour rendre leur usage plus agréable. Prenons par exemple le type des dates du module Unix.
Clairement, nous n'avons pas envie d'observer tous les champs dès que nous cherchons à savoir l'heure qu'il est. On peut justement n'observer que les champs souhaités :
Souvent, on n'observe qu'un enregistrement à la fois, dans ce cas plutôt qu'utiliser des noms supplémentaires pour les contenus des champs, on peut réutiliser son nom comme nom de variable :
Enfin, on peut simplement manipuler l'enregistrement comme une abstraction, et accéder au champ avec la "syntaxe pointée" :
Il n'est pas possible de modifier les champs d'un enregistrement.
En revanche, il existe une syntaxe pour créer facilement un nouvel enregistrement, qui est une copie d'un autre sauf pour quelques champs. Ainsi, pour arrondir une date à 0 seconde, on écrit :
L'argument time n'est pas modifié par cette fonction ! On ne peut jamais, jamais modifié le contenu d'un enregistrement, ceci n'est pas une exception. Simplement, une copie de time est créée, avec le champ tm_sec redéfini. On peut ainsi redéfinir autant de champ que souhaité :
Il s'agit uniquement d'un sucre syntaxique : le compilateur transforme ce code en une création d'une date complète avec les 9 champs, dont 6 sont recopiés depuis time.
Chaque champ contient une valeur, d'un type déterminé par le champ. Ce type est quelconque : un champ peut avoir le type d'une fonction, ou un type paramétré, simplement : n'importe quel type, aucune restriction. En particulier, un champ peut contenir une fonction.