OCaml¶
Warning
Ce cours a été automatiquement traduit des transparents de M.Noyer par Félix qui continue le travail fait par Lorentzo et Elowan et Mehdi, nous ne nous accordons en aucun cas son travail, ce site à pour seul but d'être plus compréhensible pendant les périodes de révision que des diaporamas.
Sommaire
-
Généralités
- Compilation
- Boucle interractive
-
Variable
- Les types de base
- Expressions conditionnelles
- Fonction
- Filtrage sur les listes
Crédits
- Ce cours de Olivier Pons
- Cette page de la documentation officielle et les remarques à propos des parenthèses. (Le vieux lien n'exsite plus donc ça vous renvoie sur la page principale d'OCaml)
- Cette introduction de Jean-Christophe Filliatre
- Quelques mots sur la compilation
Bonus pour approfondir le cours
Cette page et les \(3\) suivantes (cliquer sur page suivante) sont aussi très instructives.
Généralités¶
Compilation¶
2 Compilateurs (MP2I)¶
Il y a deux compilateurs en OCaml:
ocamlc
qui produit un bytecode ; un code compréhensible par un interpréteur appeléocamlrun
. Avantages : simplicité, portabilité et rapidité de compilation.ocamlopt
qui compile directement en langage machine (l'exécution sera alors plus rapide qu'avec l'intrepréteur). Il accepte des fichiers de type.ml
(fichiers de code) et.mli
(fichiers d'interfaces). Il produit :- Un fichier objet
.o
en langage machine ; - Un fichier
.cmx
contenant des informations pour l'édition de lien et l'optimisation des relations entre les divers modules. - Un fichier d'interface
.cmi
qui contient les signatures des types exportés vers d'autres unités de compilation.
Hello world (MP2I)¶
Dans un fichier hello.ml
écrivons
Pas besoin de fonction main()
. Pas besoin de parenthèse !
Compilons et exécutons (dans un terminal)
Sans surprise, la commande ./toto
affiche
Listons les fichiers de préfixe hello
Commentaires¶
Les commentaires en OCaml commencent par (*
et finissent par *)
.
Application d'une fonction à ses arguments¶
En OCaml, l'application d'une fonction s'écrit par simple juxtaposition de la fonction et de son argument. À la différence
de la plupart des langages où l'application de f
à x
doit s'écrire f(x)
, on se contente ici d'écrire f x
.
Mais les parenthèses autour de la chaîne de caractères sont tout simplement inutiles. Et c'est considéré comme inélégant.
En dehors des appels de fonctions, les parenthèses peuvent et doivent être utilisées lorsque les priorités des opérateurs l'exigent, comme dans l'expression 2*(1+3)
.
Affichage avec printf
¶
L'affichage en OCaml est un peu contraignant :
print_string
pour les chaînes de caractères,print_int
pour les entiers,print_float
pour les flottants...
Heureusement, OCaml emprunte au C quelques habitudes comme la fonction printf
du module Printf
.
Boucle interractive¶
Présentation¶
Le plus souvent, nous ne compilerons pas les codes .ml.
Nous nous contenterons d'utiliser une boucle interractive. Il en existe plusieurs. Faisons un rapide tour d'horizon.
Dans un terminal¶
OCaml est un langage compilé. Cependant
Observons que le type des expressions est calculé (on dit inféré) et affiché. Ici, le type unit
correspond au type void
en C.
On quitte la boucle interactive avec la commande exit(0);;
Le double point-virgule sépare les différentes décalarations et expressions à évaluer.
Interpréteurs en ligne¶
Si on n'a pas encore installé OCaml sur sa machine, on peut utiliser un interpréteur en ligne :
Raccourcis :
Ctrl+Enter
Exécute le code sélectionnéCtrl+Shif+Enter
Exécute le code entier.Ctrl+Space
montre le menu d'autocomplétion
Tuareg¶
Pour ma part, j'utilise le greffon Tuareg d'emacs pour rendre plus agréable l'utilisation de l'interpréteur OCaml.
Consulter par exemple cette page.
Variables¶
Syntaxe let <id> = <expr>
. Le plus souvent <id>
est un identificateur (il commence par une lettre minuscule ou un underscore). La partie à droite de l'égalité est une expression (elle peut être le résultat d'un calcul).
Une déclaration affecte le résultat de l'évaluation d'une expression à une varialble, et est introduite par le mot clé let
. Par exemple écrivons ceci :
Le programme calcule le résultat de 1+2
, l'affecte à la variable x
, affiche la valeur de x
, puis calcule le carré de x
, l'affecte à la variable y
et enfin affiche la valeur de y
.
x,y
sont des variables globales utilisables ailleurs dans le
programme.
À propos des variables¶
Une variable est nécessairement initialisée.
Le type de la variable n'a pas besoin d'être déclaré, il est inféré par le compilateur.
Le contenu de la variable n'est pas modifiable. Dans le code précédent, \(x\) contient \(3\) pour toute la durée du programme. La variable est immuable ou persistante (on est en paradigme fonctionnel).
Variable persistante, vraiment ?¶
Variable locale¶
En C, les variables locales sont définies dans un bloc délimité par des accolades et elles n'ont pas d'existence hors de ce bloc.
En OCaml, il n'y a pas de notion de bloc. Les variables locales d'une expression sont introduites par la construction let in
.
Les variables locales sont immuables et obligatoirement initialisées.
Portée de la variable x
:
Une déclaration locale est visible seulement dans l'expression qui suit le in
.
Les types de base¶
Types de base¶
- entiers (
int
), opérateurs :+ - * / mod
- flottants (
float
), opérateurs :+. -. *. /.
- caractères (
char
),'a','b','1' ...
avec une seule quote. - chaînes, (
string
),"abc", "b"
(double quotes), concaténation :
- booléans, (
bool
),true, false
; opérateurs :&&, || , not
- Opérateur de comparaison de valeurs pour tous les types
=, >, <, >=, <=, <>
. Polymorphes, mais les deux arguments doivent avoir le MEME type. - Opérateur de comparaison physique pour tous les types
==, !=
Quelques opérations
Type unit¶
Pour définir une fonction sans résultat, OCaml introduit le type unit
qui ne contient qu'une seule valeur, notée ()
.
C'est par exemple le type de retour de la fonction print_string
.
Il sert aussi à définir des fonctions sans argument.
Il joue un rôle comparable au type void
de C.
Types construits predéfinis¶
Les tuples (t1 * t2 * .. * tn
) où ti
est le type de la
composante \(i\) du n-uplet.
Les listes t_list
, où t
est le type des éléments.
Plus sur les listes¶
La liste vide est polymorphe.
Le module List
contient des fonctions de manipulation de listes:
Plus sur les tuples¶
Cas d'un tuple de taille \(2\) :
Tuples contenant des tuples
Déconstruire un tuple en ne prenant que certains items.
Le symbole _
indique la présence d'une composante sans la nommer.
Isomorphisme de types :
- Un élément de type
int * int * int
est un triplet d'entier. - Un élément de type
int * (int * int)
est un couple dont le premier élément est un entier et le second un couple d'entiers. - Un élément de type
(int * int) * int
est un couple dont le premier élément est un couple d'entiers et le second un entier - Les \(3\) ensembles des éléments appartenant à chacun de ces \(3\) types sont en bijection canonique. C'est un exemple d'isomorphisme de types. Malheureusement, pour OCaml, ces types sont bien distincts !
Expressions conditionnelles¶
Les mots clés if
then else
¶
En OCaml, il n'y a pas de distinction expression/instruction : il n'y a que des expressions.
Les expressions conditionnelles sont définies à l'aide de l'opérateur ternaire if e1 then e2 else e3
.
L'expression e1
doit renvoyer une valeur bouléenne.
En général (sauf cas particulier où e2
est de type unit
) la branche négative est obligatoire.
Une expression conditionnelle est polymorphe : i.e. e2
peut être de n'importe quel type, mais e3
est obligatoirement du même type que e2
.
unit
:
Fonction¶
Fonctions anonymes¶
Les fonctions en OCaml sont des valeurs comme les autres. Syntaxe : fun <id> -> <expr>
.
<id>
désigne le paramètre de la fonction. Il a une portée localisée au corps de la fonction.->
désigne le début du corps de la fonction.<expr>
est une expression quelconque qui peut utiliser<id>
.
Fonction produit \(x →x ×x\) :
Observer le type de la fonction. Il est noté sous la forme \(τ_1 →τ_2\).
Inférence de type : comme on utilise l'opérateur *
, OCaml en déduit que le type de la fonction est int -> int
.
Pour nommer une fonction :
Sucre syntaxique ; Forcer le type du paramètre¶
OCaml permet de déclarer une fonction de façon plus concise que let f = fun ...
.
On peut indiquer le type du paramètre et celui de retour :
C'est ici surtout utile pour le lecteur.
Attention aux priorités (penser aux parenthèses) :
En OCaml, l'application d'une fonction est syntaxiquement plus prioritaire que les autres opérations.
Application de fonction¶
En programmation fonctionnelle, l'application de fonction est la seule opération qui permette d'effectuer un calcul. Même 2 + 3
est en fait du sucre syntaxique pour exprimer l'application de (+)
à 2
et 3
: c.a.d (+) 2 3
Pour effectuer <expr1> <expr2>
:
- On évalue
<expr1>
. C'est nécessairement une fonction de la formefun x -> e
de type \(τ→τ′\). - On évalue
<expr 2>
. L'expression résultantev
doit être de type \(τ\). - On évalue l'expression
e
dans un environnement oùx
est associé à la valeurv
. La valeur renvoyée est de type \(τ′\).
Fonction sans argument¶
Une fonction sans argument prend en fait un unique argument de type unit
(qu'on note ()
).
f
ci-dessus peut être vue comme un alias pour l'expression print_string "coucou"
.
Les fonctions dont le type de retour est unit
sont souvent utilisées pour réaliser des affichages ou bien des mises à jour de variables mutables (tableaux, références...).
Fonction sans résultat¶
Les fonctions sans résultat sont utilisées à des fins d'affichage et/ou pour réaliser des effets de bord. Pour le moment toutes nos variables sont persistantes mais on découvrira bientôt comment créer des variables mutables.
Ordre supérieur¶
affiche
prend deux arguments : une fonction d'un
certain type (noté 'a
) vers les entiers, un élément de type 'a
. Le type de retour est unit . Le type de la fonction est donc ('a -> int) -> 'a -> unit = <fun>
On a vu qu'on peut passer une fonction en argument. On peut aussi renvoyer une fonction.
renvoie de l'homothétie de rapport \(x\) :
Application partielle¶
Définissons une fonction à deux arguments :
Lorsqu'une fonction a plusieurs arguments, on n'est pas obligé de les lui fournir tous lors de l'application. Si on ne le fait pas le résultat de cette application partielle est lui même une fonction qui peut éventuellement être lié a un identificateur et être appliqué par la suite.
Fonctions récursives¶
Ecrivons la fonction puissance
let puissance b n =
if n =0 then 1
else b * puissance b (n-1);;
Characters 69 -78:
else b * puissance b (n-1);;
^^^^^^^^^
Error: Unbound value puissance
Il faut prévenir le compilateur que la fonction est récursive en faisant suivre le mot clef let
du mot clef rec
.
Fonctions mutuellement récursives¶
On utilise une construction de la forme
let rec g = ... and f = ...
Calcul de parité
Filtrage sur motif de l'argument¶
Quand on connaît la forme du motif de l'argument, on peut anticiper :
Emuler des boucles¶
Pour le moment on ne présente pas les traits impératifs de OCaml. Comment émuler une boucle comme celle-ci :
On peut le faire facilement avec une fonction comme celle-ci :
Filtrage sur les listes¶
Warning
Les présents transparents ne sont qu'une introduction à OCaml. On ne présente ci-dessous que le cas particulier du filtrage sur les listes.
Listes¶
Les listes d'objets de type \(τ\) sont définies inductivement par
- la liste vide
[]
est une liste d'objets de n'importe quel type donc en particulier de type \(τ\); - Si \(e\) est de type \(τ\) et si
t
est une liste de type \(τ\), alorse::t
est une liste d'objets de type \(τ\).
On peut donc explorer une liste en la déconstruisant : on sépare son élément de tête du reste de la liste ; on traite l'élément de tête et on applique le même traitement au reste de la liste.
Conséquence de la définition inductive¶
On a vu comment sont construites inductivement les listes. Il devient possible de les explorer.
Rappel : List.tl t
donne la queue de liste (tout sauf le premier élément)
On pourrait aussi écrire, de façon plus proche de la définition inductive :
Mais on recevrait un Warning pour risque de filtrage non exhaustif dans let _::l = t
Instruction de filtrage match with¶
L'exemple simpliste du calcul de la longueur pourrait faire penser qu'on peut résoudre élégamment tous les problèmes sur les listes avec les opérateurs if then else
.
Mais on risque vite d'avoir une cascade inesthétique de
if then else
.
Pour éviter cela, l'instruction match with réalise un filtrage de motif. Elle permet soit de gérer différents cas en fonction des valeurs d'une expression, soit d'accéder aux éléments d'un type construit (ou les deux à la fois).
Plus lisible que if then else
en cascades match with
effectue en outre une analyse d'exhaustivité. Il vérifie que tous les cas possibles ont bien été couverts ce qui est très utile pour le débugage.
Calcul de longueur avec filtrage
Observer le _::t
qui permet de ne pas tenir compte de la valeur de l'élément de tête, seulement de son existence.
Filtrage sur des tuples¶
Le filtrage de motif permet soit de gérer différents cas en fonction des valeurs d'une expression, soit d'accéder aux éléments d'un type construit (ou les deux à la fois). La fonction suivante filtre ses arguments (une paire) pour faire leur addition en tenant compte du cas où l'un d'entre eux est zéro :