Laboratoire 1: Premiers pas avec OCaml
1. Installation d’OCaml
Installez OCaml sur votre ordinateur. Pour installer OCaml, il est recommandé d’installer opam
le gestionnaire de paquet d’OCaml d’abord, qui vous permettra ensuite d’installer une version spécifique d’OCaml.
1.1 Installation d’OPAM
Sur Linux
Installez le paquet opam
de votre distribution :
apt-get install opam
sur Ubuntu et Debian- référez-vous à la documentation de votre distribution pour les autres
Sur MacOS
Installez le paquet opam
avec Brew (brew install opam
) ou MacPorts (port install opam
), ou lancez la commande suivante :
bash -c "sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)"
Sur Windows
Utilisez WSL et référez-vous à la procédure d’installation sous Linux. Vous pouvez avoir plus d’information sur la procédure spécifique à Windows dans le livre de référence
1.2. Activation d’OPAM
Dans le cours, on utilisera la version LTS courante d’OCaml. Référez-vous au site web du langage pour voir quel est la dernière version Long Term Support. Nous allons considérer que la dernière version LTS (en date de janvier 2025) est la 4.14.2.
Lancez les commandes suivantes :
opam init # Répondre "y" quand opam demande de modifier votre fichier d'initialisation du shell
opam update
opam switch create 4.14.2 # Utiliser ici la dernière version LTS
eval $(opam env)
opam install utop
Vous vous retrouverez alors avec une installation fonctionnelle (basique) d’OCaml.
Depuis les salles du LAMISS
Si vous faites ces opérations depuis les salles du LAMISS, le système de fichier de votre dossier personnel étant particulièrement lent, il est recommandé de changer OPAMROOT
avant de faire opam init
:
export OPAMROOT=/tmp/$(whoami)-opam/
opam init # Répondre "y" quand opam demande de modifier votre fichier d'initialisation du shell
opam update
opam switch create 4.14.2 # Utiliser ici la dernière version LTS
eval $(opam env)
opam install utop
Il faudra répéter ces opérations à chaque login.
Une autre option plus simple est d’utiliser l’interprète en ligne.
1.3. Installation des outils de développement
Lancez la commande suivante pour installer les outils utiles au développement.
opam install dune merlin ocaml-lsp-server odoc ocamlformat utop dune-release utop
Vous pouvez ensuite configurer votre éditeur préféré en vous référant à sa documentation. On vous recommande fortement l’un des éditeurs qui possède des outils intégrés: Vim, Emacs, ou VS Code. Référez-vous à la documentation officielle pour les configurer.
1.4. Vérification de l’installation
Lancez la commande utop
. Vous devriez vous retrouver dans le toplevel.
2. Utilisation du toplevel
Le but de cet exercice est de se familiariser avec OCaml et les types de base.
Nous allons entrer des expressions que l’interpréteur évaluera (quand elles
se terminent par ;;
).
Quand on parle du toplevel, on fait référence à l’outil utop
. Les commandes sont à lancer dans cet interpréteur.
-
Lancer le toplevel et évaluer l’entier
33
. Ne pas oublier le;;
final pour évaluer l’expression. -
Déterminer à la main les types des expressions suivantes. Vérifier en utilisant le toplevel et commenter. Expliquer le message d’erreur quand il y en a un.
2
2.0
2,0
2;0
(2, 0)
(2; 0)
a
'a'
"a"
true
()
[]
[1]
[1, true]
[1; true]
- Donner des exemples d’expressions ayant les types suivants. Vérifier avec le toplevel et commenter.
int * float
string list
bool list * string
int list list
- Que valent les calculs suivants ? Après avoir répondu à la main, tester au toplevel et expliquer.
1 + 2
1.1 + 2.2
1.1 + 2
2 / 3
7 mod 2
7. mod 3.
int_of_float (2. ** 3.)
2 = 3
'a' = 'b'
"a" = 'a'
not 1 = 0
not (1 = 0)
- Utiliser le toplevel pour tester si les opérateurs booléens et (
&&
) et ou (||
) sont séquentiels (c’est à dire que la partie gauche est évaluée d’abord et la partie droite seulement si nécessaire). Donner les tests réalisés pour s’en assurer. Il est possible pour cela d’utiliser la fonctionprint_string
qui affiche la chaîne de caractères en argument.
3. Liaison locale
Dans le paradigme fonctionnel, la notion de variable (et donc d’affectation) n’existe pas. Pour nommer des expressions, on utilise le mécanisme de liaison. La liaison n’est pas une affectation mais elle en a certaines propriétés.
Pour réaliser une liaison locale, on utilise la syntaxe suivante :
let nom = expr1 in expr2;;
Ceci est une expression et possède donc une valeur : c’est la valeur de expr2
dans
laquelle toutes les occurrences (libres) de nom sont remplacées par expr1
.
Le mot-clé and
permet de faire des liaisons simultanées :
let nom1 = expr1 and nom2 = expr2 in expr3;;
-
Considérons un cylindre de hauteur h et un rayon r. Commencer par calculer l’aire d du disque qui forme la base du cylindre (c.-à-d. π r²) en une seule expression et en utilisant des liaisons locales pour π, r et h.
Il est possible d’obtenir la valeur de π en utilisant la fonction arc cosinus (en utilisant donc la liaison locale
let pi = acos (-1.0) in ...
). -
Reprendre l’expression précédente pour calculer, toujours en une seule expression, le triplet (p,a,v) où p est le périmètre de la base (c.-à-d. 2π r), a est l’aire du cylindre (c.-à-d. 2d + ph) et v son volume (c.-à-d. dh). Bien entendu, les expressions apparaissant dans plusieurs calculs différents ne doivent être calculées qu’une seule fois.
4. Différentes liaisons
Il existe un deuxième mécanisme de liaison — la liaison globale — permettant notamment de définir des constantes et les fonctions. Attention, une liaison locale est une expression alors qu’un liaison globale ne l’est pas (il s’agit d’une définition). Une liaison globale ne réalise pas non plus un effet de bord : sa raison d’être est de lier un nom à une valeur. Grâce au principe de transparence référentielle, toutes les occurrences du nom considéré peuvent se remplacer dans les expressions futures sans changer leur comportement.
Pour réaliser une liaison globale, on utilise la syntaxe suivante dans le toplevel :
let nom = expr;;
Dans un fichier source (.ml
), la présence du ;;
n’est pas obligatoire.
Comme précédemment, le mot-clé and
permet de faire des liaisons
simultanées_:
let nom1 = expr1 and nom2 = expr2;;
- Peut-on écrire les morceaux de code suivants ? Réfléchir avant et après avoir testé !
let a = 1 in a + 2;;
a + 3;;
let b = 5;;
let b = 5.5;;
let c = 1;;
let d = c;;
c + d;;
let e = 1 let f = e;;
let g = 1 and h = g;;
let a = 1 in let b = a;;
let a = 1 in let b = a in b;;
- Qu’affiche le code suivant ? Réfléchir avant et après avoir testé !
let a = 1;;
let a = 1.2 in a;;
a;;
let a = 1 in
let a = 2 and b = a in
a + b;;
5. Conditions
La structure de contrôle conditionnelle en OCaml possède la syntaxe suivante :
if expr1 then expr2 else expr3
Attention, il s’agit d’une expression, elle a donc un type et une valeur (c’est l’équivalent
du if
ternaire du C ou Java : expr1 ? expr2 : expr3
). L’expression expr1
est de type bool
et les
expressions expr2
et expr3
doivent être du même type (qui est également le type
de l’expression complète).
- Calculer le maximum entre les entiers
a
etb
. - Calculer le minimum entre les entiers
a
,b
etc
. - Que penser du code suivant ? Réfléchir avant et après avoir testé !
if a mod 2 = 0 then a else "odd";;
- Qu’est-ce qui ne va pas dans le code suivant ? Réfléchir avant et après avoir testé !
if a < 10 then let b = "small" else let b = "large";;
-
Écrire le code correct qui permet de lier le nom
b
à l’une des chaînes"small"
ou"large"
en fonction de la valeur dea
. Se rappeler pour cela qu’une expression conditionnelle possède une valeur et qu’elle peut donc, toute entière, être liée à un nom. -
Étant donné un entier
a
, en utilisant une expression conditionnelle, donner le code permettant d’obtenir b = ⌈a / 2⌉. Rappelons que ⌈·⌉ est la fonction « partie entière supérieure ». -
Étant donnés trois entiers
a
,b
etc
, calculer l’expression suivante sans faire deux fois le même test ou le même calcul :
- min(a, b)² + 1 si c est divisible par 3
- min(a, b)² sinon
6. Fonctions
En OCaml, on dispose d’un certain nombre de bibliothèques prédéfinies (sous la forme de
modules). Le module Stdlib
est chargé par défaut et sa documentation se trouve à
l’adresse suivante : https://ocaml.org/api/Stdlib.html.
D’autres bibliothèques classiques et de base sont documentées à partir de l’adresse suivante : https://ocaml.org/api/.
Dans cet exercice, nous allons définir nos premières fonctions. Celles-ci sont à écrire dans
un fichier labo1.ml
. Le contenu de ce fichier se charge dans le toplevel par la
commande suivante (attention à ne pas oublier le #
) :
# #use "labo1.ml";;
Pour appliquer une fonction à des arguments, il suffit de donner son expression suivie des arguments sans parenthèses, contrairement aux autres langages usuels. Par exemple,
int_of_float 3.4;;
mod_float 3.4 2.0;;
(+) 1 2;;
String.make 5 'A';;
(fun x -> x + 1) 4;;
Il est possible de parenthéser globalement une application de fonction lors d’applications imbriquées. Par exemple,
max 1 (max 2 3);;
Enfin pour définir ses propres fonctions, on peut utiliser (entre autres) la syntaxe suivante :
let nom p1 ... pn =
expr
(* `expr` est le corps de la fonction. *)
-
Écrire une fonction
double
qui multiplie son argument (entier) par deux. -
Écrire et tester une fonction
average
qui calcule la moyenne de trois entiers. Cette fonction peut-elle être utilisée avec des flottants ? -
Écrire une fonction
implies
qui prend deux expressions booléennesa
etb
et renvoie vrai sia
impliqueb
au sens logique. -
Écrire une fonction
inv
qui prend un couple et renvoie le couple inversé. Faire deux versions : l’une en utilisantfst
etsnd
et l’autre sans. -
Écrire la même fonction, mais qui ne fonctionne uniquement que pour des couples d’entiers. Vérifier son type.
-
Écrire la fonction
f_one
qui prend la valeur unité (()
de typeunit
) en argument et qui renvoie la constante 1. Vérifier qu’elle a bien le type attendu. -
Définir une fonction
sign
qui retourne le signe d’un entier :-1
si l’argument est négatif,0
s’il est zéro,1
s’il est positif. -
Qu’affiche le code suivant (pour chaque ligne) ? Réfléchir avant et après avoir testé !
let m = 3;;
let f x = x;;
let g x = x + m;;
f 4;;
g 4;;
let m = 5;;
g 4;;
f m;;
7. Utilisation d’un éditeur
- Créer un fichier
hello.ml
avec votre éditeur favori, et y insérer le contenu suivant :
let f x = x + 5
let _ =
print_endline "Hello, world!"
-
Lancer UTop depuis le même dossier que le fichier
hello.ml
. Dans UTop, taper#use "hello.ml";;
. Que se passe-t-il ? -
Depuis UTop, appeler la fonction
f
avec un argument. -
Quitter UTop pour retourner au terminal (avec Contrôle-D ou en tapant
#quit;;
). Exécuter le fichier avec l’interpréteur en tapant la commandeocaml hello.ml
. Quel est le résultat ? -
Compiler le programme en tapant la commande
ocamlc hello.ml -o hello
. Exécuter le binaire produit.