INF6120 - Laboratoire 1

Sommaire

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

Lancez les commandes suivantes :

opam init # Répondre "y" quand opam demande de modifier votre fichier d'initialisation du shell
eval $(opam env)

Vous vous retrouverez alors avec une installation fonctionnelle (basique) d’OCaml.

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 éditer en vous référant à sa documentation.

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.

  1. Lancer le toplevel et évaluer l’entier 33. Ne pas oublier le ;; final pour évaluer l’expression.

  2. 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]
  1. 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
  1. 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)
  1. 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 fonction print_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;;
  1. 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 ...).

  2. Reprendre l’expression précédente pour calculer, toujours en une seule expression, le triplet (p,a,v)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;;
  1. 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;;
  1. 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).

  1. Calculer le maximum entre les entiers a et b.
  2. Calculer le minimum entre les entiers a, b et c.
  3. Que penser du code suivant ? Réfléchir avant et après avoir testé !
if a mod 2 = 0 then a else "odd";;
  1. 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";;
  1. Écrire le code correct qui permet de lier le nom b à l’une des chaînes "small" ou "large" en fonction de la valeur de a. 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.

  2. É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 ».

  3. Étant donnés trois entiers a, b et c, 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. *)
  1. Écrire une fonction double qui multiplie son argument (entier) par deux.

  2. Écrire et tester une fonction average qui calcule la moyenne de trois entiers. Cette fonction peut-elle être utilisée avec des flottants ?

  3. Écrire une fonction implies qui prend deux expressions booléennes a et b et renvoie vrai si a implique b au sens logique.

  4. Écrire une fonction inv qui prend un couple et renvoie le couple inversé. Faire deux versions : l’une en utilisant fst et snd et l’autre sans.

  5. Écrire la même fonction, mais qui ne fonctionne uniquement que pour des couples d’entiers. Vérifier son type.

  6. Écrire la fonction f_one qui prend la valeur unité (() de type unit) en argument et qui renvoie la constante 1. Vérifier qu’elle a bien le type attendu.

  7. 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.

  8. 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

  1. 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!"
  1. Lancer UTop depuis le même dossier que le fichier hello.ml. Dans UTop, taper #use "hello.ml";;. Que se passe-t-il ?

  2. Depuis UTop, appeler la fonction f avec un argument.

  3. 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 commande ocaml hello.ml. Quel est le résultat ?

  4. Compiler le programme en tapant la commande ocamlbuild hello.ml. Exécuter le binaire produit.