Skip to content
Snippets Groups Projects
tp1.md 14.17 KiB

TP1: Découverte du langage OCaml

0. Environnement de travail

Les TPs s'effectueront sous Linux.

Vérifier que ocaml est disponible.

TODO: ajouter les instructions pour le opam partagé

Vérifier que l'extension "OCaml Platform" est installée dans VSCode, l'installer si besoin.

Créer un fichier tp1.ml, ouvrir ce fichier avec Visual Studio Code. Le plugin OCaml permet d'exécuter un code sélectionné avec les touches Shift Enter.

1. Premiers pas avec l'interpréteur

Lancer l'interpréteur ocaml avec la commande

rlwrap ocaml

ou simplement

ocaml

si rlwrap n'est pas installé.

1.1. Types de base

On va commencer par utiliser l'interpréteur comme une calculatrice avancée. Dans l'interpréteur, les expressions à calculer doivent se terminer par ;;.

Saisir quelques opérations sur les entiers, par exemple:

3*2+5/2;;

On remarque qu'en plus du résultat, l'interpréteur nous indique son type, ici int.

On peut également faire des opérations sur des nombres flottants, mais il faut ajouter aux opérateur un .. Par exemple, pour OCaml + ne fonctionne que pour les int et c'est +. qu'il faut utiliser pour additioner des float.

Saisir quelques opérations arithmétiques sur des float.

OCaml possède également un type string (délimitées par des doubles quotes, par exemple "abcd"). On peut concaténer des chaînes avec l'opérateur ^).

Saisir quelques chaînes de caractères assemblées par concaténation.

Il est évidement possible d'utiliser les booléens en OCaml. Les constantes sont true et false et les opérateurs sont && et ||.

Écrire une ou deux expressions booléennes dans l'interpréteur. Indiquer qui de || ou && est prioritaire (c.-à-d. où sont les parenthèses dans une expression comme a || b && c)

Les opérateurs de comparaison s'écrivent = (attention il y a bien un seul =), !=, >, <, >=, <=. Ils permettent de comparer des valeurs de même type.

Expliquer la différence de comportement entre ces deux expressions:

3 < 2;;

et

3 < 2.0;;

1.2. Appels de fonction

En OCaml, les appels de fonctions sont sous la forme f a1 a2 a3f est la fonction et a1, a2 et a3 sont les arguments (les paramètres effectifs) passés à la fonction. Cette syntaxe ressemble à celle d'une commande invoquée avec des arguments dans un shell Unix. Elle peut également rappeler la syntaxe d'appel des fonctions en Scheme, mais sans les parenthèses. Il sera cependant parfois nécessaire de placer des parenthèses autour d'un argument si celui-ci est lui-même un appel de fonction, par exemple dans l'expression f (f2 a1) a2 a3, le premier argument f2 a1 est placé entre parenthèses.

La fonction float_of_int permet de transformer un int en float.

Dans l'interpréteur, appeler la fonction float_of_int en lui passant un int en argument. Essayer ensuite avec un float et expliquer ce qui c'est passé.

Entrer l'expression int_of_float ;; dans l'interpréteur. Celui-ci affiche alors le type de la fonction.

Les opérateurs des expression arithmétiques sont moins prioritaires que les appels de fonction. Par exemple int_of_float 3.5 + 2 doit être lu comme (int_of_float 3.5) + 2.

Écrire une expression combinant des opérateurs arithmétiques et ces deux fonctions.

La négation dans les booléens n'est pas un opérateur, mais simplement la fonction not.

Expliquer le résultat de l'évaluation de not false && false en ajoutant les parenthèses implicites au bon endroit.

Remarque: les opérateurs en OCaml sont aussi des fonctions. En ajoutant des parenthèses directement autour d'un opérateur, il sera vu comme une fonction, par exemple on peut écrire (&&) true false au lieu de true && false.

Récrire l'expression suivante en utilisant les opérateurs comme des fonctions:

2 < 3 && "b" < "ab"

1.3. Variables

Pour rappel, la syntaxe des définitions de variables est :

let ma_variable = sa_valeur in expression_qui_utilise_ma_variable

par exemple, saisir dans l'interpréteur:

let x1 = 3.5 +. float_of_int 2
in x1 +. 3.0;;

La construction let in est elle-même une expression et peut donc être utilisée dans d'autres expressions, y compris d'autres let ... in .... Cela permet en particulier de définir plusieurs variables à la suite:

let x1 = 3.5 +. float_of_int 2 in
let x2 = x1 *. x1 in
x2 *. 2.0 ;;

1.4 Conditionnelle

La conditionnelle if .. then ... else ... est une expression et pas une instruction en OCaml, c'est à dire qu'elle a un résultat.

Prédire puis vérifier le résultat de l'expression suivante:

(if true then 3 else 5) > 4

2. Définition de fonctions

La construction let sans le in permet de définir des variables globales. On peut l'utiliser pour définir des fonctions comme suit:

let f a1 a2 a3 = valeur_de_retour;;

Par exemple une fonction f définie en \lambda-calcul par \lambda x.(x+1) peut se définir en ocaml par:

let f x = x + 1;;

Définir cette fonction dans tp1.ml puis l'utiliser dans une expression.

Définir une fonction discriminant qui prend les trois coefficients float d'un trinôme ax^2+bx+c et en calcule le discriminant (b^2-4ac).

Quel est le type affiché par OCaml à la définition de cette fonction ?

Il est possible d'imposer les types des arguments avec la syntaxe suivante:

let f (a1:t1) (a2:t2): type_retour = valeur_de_retour;;

Récrire la fonction discriminant en forçant le type des arguments à être des float. Tester que cette nouvelle définition marche dans l'interpréteur par exemple avec 2.0 8.0 et 8.0 comme valeurs de a, b et c.

Récrire une deuxième fois cette fonction en changeant le type de a en int sans changer le reste de la fonction. Expliquer l'erreur produite par l'interpréteur.

Dans la suite de ce TP et dans les TPs suivants, on prendra soin de toujours indiquer le type des fonctions définies globalement.

OCaml est muni d'un outil, ocamldoc, qui permet de générer de la documentation à partir de commentaires spéciaux.

Voici une exemple de documentation à placer juste avant la définition de discriminant:

(**
[discriminant a b c] calcule le discriminant d'un trinôme.
Cette fonction est utile pour trouver les racines d'un trinôme.
@param a le coefficient d'ordre 2
@param b le coefficient d'ordre 1
@param c le coefficient d'ordre 0
@return le discriminant
*)

Dans la suite de ce TP et pour les autres TP, on écrira toujours un commentaire spécial avant la définition d'une fonction définie globalement. Et on écrira toujours ce commentaire avant de commencer à coder la fonction.

3. Types somme

3.1 Définitions de types somme

Les types somme en OCaml sont des types dont les valeurs peuvent être construites de différentes manières. L'exemple suivant illustre la syntaxe de définition de ces types:

type couleur = Rouge | Jaune | Bleu;;

Ici on définit un type couleur avec trois valeurs possibles (Rouge, Jaune et Bleu).

Entrer les expressions suivantes dans l'interpréteur et expliquer ce qu'il affiche:

Rouge;;
Rouge = Rouge;;
Rouge != Bleu;;
Bleu > Jaune;;

Rouge, Jaune et Bleu sont appelés les constructeurs du type couleur.

Ajouter les couleurs Violet, Orange et Vert aux couleurs possibles en redéfinissant le type couleur.

Il est possible d'avoir des valeur plus riches en leur associant une ou plusieurs valeurs d'autres types. Par exemple on peut écrire la définition suivante:

type mes_valeurs =
  UnInt of int
| DeuxInt of int * int
| UneChaine of string
;;

Les constructeurs prennent ici des arguments, par exemple on peut fabriquer des valeurs de type mes_valeurs comme suit:

UnInt 3;;
UneChaine "toto";;
DeuxInt (3,4);;

Ajouter un constructeur supplémentaire RJB au type couleur. Ce constructeur devra prendre 3 int en argument.

Remarque: quand on passe plusieurs arguments à un constructeur, ils sont passés sous forme de n-uplet (une paire pour DeuxInt et un triplet pour RJB). Les n-uplets sont prédéfinis en OCaml et possèdent leur propre type dont la syntaxe est *

En utilisant l'interpréteur, indiquer quel est le type de (3, 5.6). Quelle est la différence avec (3,5,6) ?

3.2 Pattern matching

OCaml possède la construction match ... with ... qui permet de décider de l'expression à évaluer en fonction de la valeur de l'expression indiquée dans le match. Par exemple:

match c with
| Rouge -> "rouge"
| Bleu -> "bleu"
| Jaune -> "jaune"
| _ -> "mélange"

s'évalue en "rouge" si c contient Rouge ou encore sur "mélange" si c n'est ni Rouge, ni Jaune, ni Bleu.