Cours 1

Htmligure
Version & licenses
Creative Commons License

Couples, triplet et au-delà

Guyslain
Thursday, 21 July 2016

Retour au sommaire.

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.

Les couples

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.


  1. let vector = (1.2, 3.4)

  2. let name = ("John","Doe")

  3. let name_and_age = (identity, 42)

Les types de ces différentes variables sont

  1. val vector : float * float

  2. val name : string * string

  3. val name_and_age : (string * string) * int

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.

  1. val fst : ('first * 'second) -> 'first
  2. val snd : ('first * 'second) -> 'second

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 :

  1. let rotate_anticlockwise vector =
  2. (-. snd vector, fst vector)

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.

  1. let rotate_anticlockwise (x,y) =
  2. (-.y, x)

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 :

  1. let rotate_anticlockwise vector =
  2. let (x,y) = vector in
  3. (-.y, x)

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.

Les tuples

Pourquoi s'arrêter à seulement deux composantes ? On peut construire des tuples avec un nombre arbitraire de composantes. La syntaxe s'étend naturellement :

  1. let vector1 = (1., 3., -1.)
  2. let vector2 = (-2., 0., 1.)

  3. let vector_product (x1,y1,z1) (x2,y2,z2) =
  4. ( y1 *. z2 -. z1 *. y2,
  5. x3 *. y1 -. x1 *. y3,
  6. x1 *. y2 -. x2 *. y1
  7. )

  8. let vector3 = vector_product vector1 vector2

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 :

  1. let x_proj (x,y,z) = x
  2. let y_proj (x,y,z) = y
  3. let z_proj (x,y,z) = z

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.

Retour au sommaire.