OCaml propose plusieurs mécanismes pour créer des structures de données, que nous apprendrons tout au long du semestre. Nous commençons avec la structure la plus simple : les tuples.
Un couple permet d'encoder deux valeurs en une seule valeur. C'est donc exactement ce qu'on entend par un couple en mathématiques, et la notation est similaire : des parenthèses et une virgule.
Les types de ces différentes variables sont
Ainsi, on remarque que le type d'un couple se forme avec le symbole *, utilisé comme un opérateur binaire infixe, dont les deux termes sont les deux types des deux valeurs du couple. Dans le cas de name_and_age, les deux termes ne sont pas du même type, et le terme gauche n'est même pas un type simple. De fait, il n'y a aucune contrainte sur les types des membres droits et gauches d'un couple : on peut mettre n'importe quelles valeurs dans un couple, y compris des fonctions. Puisque le type du couple est construit à partir du type des deux termes, il reflètera bien les types des valeurs contenus. Il existe donc une infinité de types couples possibles et différents les uns des autres; le type int * int n'est pas égal au type float * float.
Les composantes droites et gauches sont identifiées, elles ne sont pas interchangeables. Ainsi, le type int * bool est différent du type bool * int.
Nous venons de voir qu'il est simple de regrouper deux valeurs en une seule. Il nous faut aussi comprendre comment faire l'opération inverse : étant donné un couple, possiblement sous la forme d'une abstraction donc d'un identifiant, comment retrouver les deux valeurs formant le couple ? Le plus souvent, le problème se pose d'écrire une fonction prenant un couple en argument. Considérons le cas d'une fonction ayant pour paramêtre un vecteur de dimension 2 , encodé par un couple de flottants. La fonction doit calculer sa rotation par un angle droit dans le sens anti-horaire, c'est-à-dire si le vecteur est $(x,y)$, calculer $(-y,x)$. Nous allons proposer deux solutions.
La première solution consiste à utiliser les deux projections. Une projection est une fonction prenant un couple, ou plus généralement un tuple de plusieurs composantes, et retournant l'une des composantes du couple ou du tuple. Pour les couples, il existe donc deux projections possibles, pour obtenir la composante droite ou la composante gauche.
Les types de ces deux fonctions sont particuliers, ils font intervenir des variables de types 'first et 'second. Ce sont des exemples de fonctions polymorphes, comme la fonction identity de la section précédente. Sans entrer dans les détails, que nous verrons plus tard, on peut utiliser ces fonctions dans n'importe quel contexte où est attendu des fonctions d'un type obtenu en remplaçant 'first et 'second par des types arbitraires. Par exemple, en prenant 'first = int et 'second = float, on déduit que fst peut être utilisé comme une fonction de type (int * float) -> int et second peut être utilisé comme une fonction de type (int * float) -> float.
Ainsi, pour notre problème de rotation de vecteur, voici une première solution :
Cette solution utilise un argument vector pour abstraire le couple, puis les fonctions de projections pour récupérer les valeurs qui nous intéressent dans le but de calculer la rotation.
La deuxième solution ne nécessite pas d'utiliser les projections. Puisque nous avons choisi de représenter les vecteurs comme des couples, nous savons que l'argument donné à la fonction rotate_clockwise est une expression dont la valeur est un couple. Et nous savons que les valeurs de couples sont formées comme deux valeurs, séparés par une virgule, entourées de parenthèses. OCaml a la capacité de nous redonner les valeurs telles qu'elles ont été définies : nous pouvons directement récupérer le paramètre avec la même syntaxe. On parle d'observation du couple.
Attention, rotate_anticlockwise est une fonction a un seul argument. Son type est (float * float) -> (float * float). Cela ressemble à une fonction à deux arguments de Java, mais ici c'est un seul argument, qui est un couple, et qui est observé en tant que couple. On choisit donc d'abstraire séparément les deux composantes du couple, par les identifiants x et y. Ces identifiants peuvent ensuite être directement utilisés pour calculer la rotation.
Souvenons-nous : définir une abstraction ou prendre un argument dans une fonction, ce sont les mêmes principes qui sont à l'œuvre. Il est donc naturel que cette syntaxe soit aussi disponible lors de la création d'une variable, comme dans cette troisième version :
Ici, l'observation du vecteur comme un couple est retardée en ligne 2 , avec la création de deux variables expression-locales grâce à l'observation du couple.
Nous verrons par la suite que l'observation n'est pas une propriété spéciale des couples : toutes les valeurs non-fonctionnelles peuvent être observées en OCaml, c'est-à-dire on peut retrouver de quelle façon ces valeurs ont été construites.
Pourquoi s'arrêter à seulement deux composantes ? On peut construire des tuples avec un nombre arbitraire de composantes. La syntaxe s'étend naturellement :
Ici le type des vecteurs en 3 dimensions est (float * float * float). vector_product est une fonction à deux arguments, chaque argument est un triplet. Attention, ce n'est pas le même type que (float * float) * float, qui est le type des couples dont la première composante est un couple de deux flottants, et la deuxième composante est un flottant.
Pour les triplets et les tuples encore plus grands, il n'existe pas de projections pré-définies. On peut les définir soi-même :
Plus le nombre de composantes est grand, moins les tuples sont pratiques à manipuler. De fait nous verrons comment créer des structures de données plus sophistiquées pour représenter des données plus complexes. Ainsi on utilisera les couples abondamment, en particulier pour écrire des fonctions retournant deux valeurs, mais nous utiliserons très peu les triplets et les autres tuples.