Ce premier TP nous permet de découvrir un environnemement de programmation en OCaml, comment mettre en place un projet. Nous travaillerons ensuite la syntaxe des fonctions et de leur application. Pour nous motiver, nous utiliserons une librairie nous permettant de contruire des structures façon Lego ® , par empilement de briques rectangulaires.
Nous allons utiliser les outils suivants :
Pour ceux souhaitant travailler sur leur machine personnnelle, commencez par installer OCaml sur votre système.
Ouvrez un terminal, et lancez l'interpréteur interactif utop. Quelle version d'OCaml est utilisée par l'interpréteur ? Calculez le produit de $193228$ et $55203$ (terminez par ;;). En cas de problème, OCaml (ou utop) ne doit pas être correctement installé : vérifiez l'installation et au besoin demandez de l'aide. Pour quitter l'interpréteur, faites exit 0;;.
Commencez par créer un répertoire dédié au TP de ce jour. Nous allons utiliser plusieurs librairies, donc nous allons commencer par signaler leur présence à l'outil de compilation et à l'environnement de développement.
Créez un fichier nommé _tags. Ce fichier indique à l'outil d'automatisation de la compilation (ocamlbuild) quelles librairies nous utilisons. Nous allons faire de la géométrie avec Gg et du dessin vectoriel avec Vg, nous produirons des dessins au format svg avec Vgr_svg. Le fichier _tags doit donc contenir :
Ceci indique que tous les fichiers dépendent des trois librairies citées.
Créez un fichier nommé .merlin. Merlin est un outil utilisé par les environnements de développement pour réaliser l'auto-complétion, la détection d'erreurs, les indications de types. Nous devons aussi dire à Merlin quelles librairies sont utilisées dans le projet, et où se trouvent les fichiers compilée du projet lui-même. Le contenu du fichier .merlin pour ce projet est donc :
Il existe plusieurs éditeurs supportant OCaml. Dans les salles de TP de l'université, vous trouverez Emacs et Atom. Il est vivement recommandé d'utiliser un éditeur supportant Merlin. Atom offre une expérience un peu plus moderne qu'Emacs en général, mais Emacs est plus confortable avec OCaml. Ici se trouve des instructions pour utiliser Emacs. Choisissez votre éditeur, les instructions données par la suite sont valables pour Emacs et devront être adaptées pour Atom. Ouvrez l'éditeur de votre choix (les instructions par la suite sont donnés pour Emacs), et créer un fichier firstFile.ml.
Assurez-vous de travailler avec deux zones de texte dans l'éditeur (raccourcis Emacs C-x 1 et C-x 3). L'une d'elles contiendra une interface avec l'interpréteur. Recopiez les définitions suivantes :
Si le code est mal-indenté, sélectionnez tout et appuyez sur la touche tab : Emacs réindente alors tout le code sélectionné correctement. Sauvegardez le fichier. Merlin devrait vous indiquer une erreur (sinon, Merlin est mal configuré, vérifiez l'installation). Corrigez l'erreur (indice : fact est une définition récursive).
Positionnez le curseur sur la définition de a. Le raccourci pour évaluer une définition est C-c C-e, ce qui dans la notation usuelle d'Emacs indique qu'il faut taper control et c simultanément, puis control et e simultanément.
Pour la première évaluation, Emacs doit choisir quel interpréteur utiliser. Il affiche dans la barre en bas de l'écran le choix par défaut, qui devrait être correct, validez en tapant entrée. L'une des zones de textes devrait à présent contenir la boucle d'interaction OCaml, en particulier le résultat de l'évaluation de la définition de a.
Emacs a modifié la position du curseur pour se placer sur la définition suivante, on peut donc recommencer C-c C-e deux fois pour évaluer les deux définitions restantes.
Si toutes ces étapes ont réussi, vous êtes prêt à commencer le TP. Sinon, réessayez ou cherchez de l'aide.
Voici les sources fournies pour ce TP. Décompressez-les dans le répertoire dédié à cette séance de TP.
Les fichiers sources sont compilés en bytecode (ou en code natif). Les fichiers de bytecode ont pour extension cmo (cmx en natif) ou pour une collection de modules l'extension cma (cmxa en natif). Les exécutables ont l'extension byte ou native.
Plusieurs systèmes d'automatisation de la compilation peuvent être utilisées avec OCaml. En particulier on peut tout-à-fait utiliser make en écrivant un Makefile approprié. Nous allons plutôt utilisé des outils dédiés à OCaml, et en particulier ocamlbuild qui est adapté aux petits projets, en conjonction avec ocamlfind.
ocamlfind est un utilitaire permettant de trouver les répertoires d'installations des librairies OCaml. ocamlbuild est un outil d'automatisation de la compilation assurant la gestion des dépendances. Le principe est très simple : pour obtenir le fichier target, il suffit de lancer la commande :
Si tout se passe bien, target sera alors un fichier dans le répertoire _build.
Produisez ainsi le fichier lego.cma. Listez les fichiers du répertoire _build. Le fichier lego.cma est la réunion de tous les modules compilés d'extension cmo dont le nom est donné dans le fichier lego.mllib. C'est une façon simple de compiler (puis ensuite de charger) un ensemble de modules.
La librairie fournie permet de réaliser des constructions en 2 dimensions à la façon des legos, par empilage de briques rectangulaires. Nous utiliserons les modules suivants (les autres sont utilisés en interne, nous pouvons les ignorer) :
Chaque module est défini par deux fichiers : le source est dans le fichier d'extension ml. Il n'est normalement pas nécessaire de le consulter (mais soyez curieux !). Le fichier d'extension mli de même nom définit ce qui est exporté par le module, donc la partie publique du module. Il contient aussi en général la documentation sous la forme de commentaires. Vous pouvez directement utiliser les fichiers mli pour consulter la documentation. Ou bien vous pouvez générer la documentation avec la commande suivante, utilisant le fichier lego.odocl contenant les noms de modules à inclure dans la documentation :
Vous pouvez ensuite ouvrir le fichier généré dans un navigateur pour consulter la documentation.
Nous allons commencer par la plus simple des constructions : une simple brique rectangulaire. Le générateur de brique est Generate.block :
Créez un fichier one_brick.ml, contenant le programme suivant :
Remarquons que pour accéder aux noms définis dans un module, on peut le préfixer par le nom du module (par exemple Generator.block), ou localement ouvrir le module comme on l'a fait ici (let open ... in).
block prend deux arguments nommés de types entiers. Ainsi, à l'utilisation, le rôle de chaque argument est clair. Les noms sont appelés label.
Nous définissons une valeur main, mais c'est uniquement par respect pour les conventions. Cet identifiant n'est pas traitée différemment des autres, vous pouvez donc le changer par n'importe quel autre nom. C'est au moins pratique pour indiquer notre intention que cette valeur soit la raison d'être de notre programme.
Compiler le programme en un exécutable one_brick.byte (ou one_brick.native), et exécutez-le. Un fichier out.svg devrait avoir été créé, vous pouvez l'ouvrir dans un navigateur. Vérifiez qu'il y a bien un rectangle noir.
Nous pouvons aussi évaluer le programme sans le compiler, ce qui sera plus pratique pendant le développement pour prototyper les fonctions que nous écrirons. Pour cela, il faut charger les librairies nécessaires dans le toplevel. On utilise des directives, qui sont des commandes propres au toplevel (et ne font donc pas partie du langage OCaml). Pour ne pas avoir à les retaper à chaque fois que nous ouvrons le toplevel, nous les mettons dans un fichier .ocamlinitdans le répertoire de travail. (Vous pouvez aussi créer un .ocamlinit dans votre $HOME, qui sera systématiquement chargé par le toplevel, et qui peut par exemple contenir vos fonctions préférées).
Pour avoir une brique d'une autre couleur, on utilise une fonction qui prend la brique et construit une brique similaire, d'une autre couleur. Cela marche en fait sur n'importe quelle construction, en modifiant la couleur de chaque brique élémentaire dans la construction obtenue.
On trouve dans Generator les fonctions suivantes :
Remarquez le type des 5 fonctions de couleurs : elles prennent un générateur et produisent un nouveau générateur. On peut donc par exemple écrire (et évaluer):
On utilise ici la syntaxe usuelle de l'application en OCaml : la fonction séparée de l'argument par un espace f arg. Puisque l'argument est une expression complexe, on le parenthèse. Cependant, la librairie de simili-lego est conçue pour être utilisée de préférence avec l'opérateur d'application inversé : le pipe |>. Dans ce cas, la syntaxe est arg |> f , ce qui pour nous donne :
Pour les formes, cela fonctionne pareil, voici les combinateurs de formes :
Notons alors comment la syntaxe change si on veut une brique bleue et triangulaire :
On voit notamment comment l'opérateur |> permet de limiter l'usage de parenthèses, ce qui rend l'expression plus lisible.
Quelques questions avant de voir comment assembler des briques :
Voici les fonctions permettant d'assembler des blocs de briques :
Imaginons que nous souhaitions avoir une brique bleue, puis à droite une brique rouge. Autrement dit on veut placer la brique bleue à gauche de la brique rouge :
Évaluez, vous devriez obtenir :
Notons que la première brique donnée ne change pas de position, donc si on utilisait right_of, la brique rouge serait à gauche de la brique bleue, donc hors de l'image (essayez !).
On peut répéter pour avoir une troisième brique :
Quel motif doit-on obtenir ? Vérifiez-le.
Pour empiler verticalement des briques, le principe est le même, avec les fonctions above et below. Essayez d'empiler trois briques de trois couleurs différentes.
Les fonctions left_of et right_of ont un argument optionnel gap, qui permet de créer un trou entre les briques. On peut donc placer deux briques noirs séparées par un vide de la taille de nos briques, ainsi :
On veut créer une pyramide. Soyons modeste et commençons par obtenir ceci :
Essayez de le faire. Quelle difficulté se présente ?
Lorsqu'on empile des constructions les unes au-dessus des autres, les colonnes de gauche des deux morceaux sont alignées, puis l'empilement est réalisé. Pour mettre la brique du haut au milieu, il est donc nécessaire de la pousser vers la droite avant l'empilement. Utiliser la fonction x_shift pour cela. Obtenez maintenant la petite pyramide.
Passons aux choses sérieuses, et essayons d'obtenir ça :
Pour cela, il faut commencer simplement par faire les niveaux horizontaux.
Définissez le niveau du haut, celui en dessous, et ainsi de suite. Vous allez voir un motif apparaitre. La fonction pack_horizontally permet de capturer l'essentiel de ce motif. Voici un exemple d'utilisation de cette fonction (l'argument nommé gap est optionnel). Le paramètre n de pack_horizontally doit être au moins 1 .
Compléter la fonction permettant de créer le $k$e niveau de la pyramide :
Notez comment nous définissons une fonction, avec un paramêtre k. Nous testons séparément le cas k = 0 car pack_horizontally doit prendre un argument n positif non-nul.
Il reste à empiler tous les niveaux. On serait tenté d'utiliser pack_vertically, mais les niveaux sont distincts. On va donc plutôt utiliser une récursion, en remarquant qu'une pyramide de hauteur $n$ est une pyramide de hauteur $n-1$ posée (décalée de 1 ) sur une couche de niveau $n$. Complétez le programme suivant pour obtenir une grande pyramide :
Donnons des couleurs plus authentiques à notre pyramide. Pour cela, nous allons choisir aléatoirement la couleur de chaque brique parmi une liste. Le module Colors.combinator permet de créer une liste de combinateurs de couleurs.
Tout ce qu'il nous faut changer, c'est le choix de la couleur de chaque brique. On modifie donc la définition d'une brique en utilisant la fonction sample, qui déclare que nous voulons choisir un élément aléatoire parmi ceux d'une liste.
sample choisit un élément et le passe à la fonction donnée en deuxième argument de sample. On peut aussi utiliser l'opérateur d'application @@ pour simplifier. Tout ce qui suit cet opérateur devient argument de la fonction précédent l'opérateur, ce qui permet d'intégrer facilement une ligne avec sample dans une chaîne de |> via l'utilisation d'une fonction anonyme :
Nous avons fait le tour des principales fonctionnalités de cette librairie. Essayez maintenant de créer de jolies constructions. Voici quelques exemples de réalisations :