Bibliothèques
Table des matières

Comme beaucoup d'autres distributions de compilateurs/langages, OCaml est accompagné de bibliothèques. Chaque bibliothèque peut définir de nouveaux types, nouvelles exceptions, nouvelles fonctions, nouvelles classes et nouveaux modules. Ces bibliothèques soit évitent au développeur d'avoir à réimplémenter des fonctions de base, soit fournissent des opérations non définissables dans le langage comme les fonctions d'entrées/sorties avec un nombre variable d'arguments (e.g. printf).

Elles se présentent sous forme de modules incluant éventuellement d'autres modules. Nous allons voir ici comment utiliser ces modules, tandis que la définition de nouveaux modules est détaillée dans le chapitre Programmation modulaire.

Les bibliothèques de la distribution OCaml sont séparées en trois ensembles:

1. Bibliothèque préchargée

Le module Pervasives est la bibliothèque préchargée par OCaml lors de la compilation ou l'évaluation de programmes, c'est-à-dire que tout se passe comme si en tête de chaque fichier de code figurait une instruction

# open Pervasives;;

Les éléments de Pervasives sont donc visibles et accessibles sans nécessité de les préfixer (on peut faire appel à compare au lieu de Pervasives.compare). La construction open ... est présentée dans 2.

Dans la documentation anglaise, cette bibliothèque est appelée "Core library".

Elle fournit des éléments de base dans des domaines variés. Nous en donnons quelques-uns ci-dessous mais il sera utile de parcourir la documentation du module pour avoir un bon aperçu de ce qu'il offre.

1.1. Types et exceptions

C'est dans ce module que sont définis les types de base ainsi que les exceptions d'usage général. Voir le manuel

1.2. Comparaisons

Plusieurs fonctions de comparaisons sont fournies et sont polymorphes, permettant ainsi de comparer n'importe quel couple de valeur d'un même type:

(=) : 'a -> 'a -> bool Egalité structurelle
(<>) Négation de (=)
(<) et (<=) relation d'infériorité structurelle stricte ou non
(>) et (>=) relation de supériorité structurelle stricte ou non
compare : 'a -> 'a -> int Comparaison générale:
compare a b = 0 si a = b
compare a b < 0 si a < b
compare a b > 0 si a > b
min : 'a -> 'a -> 'a Minimum structurel de deux valeurs
max Maximum structurel de deux valeurs
(==) : 'a -> 'a -> bool Egalité physique
(!=) Négation de (==)

Les fonctions entre parenthèses sont utilisables en position infixe.

Quelques exemples:

# "toto" = "tutu";;
- : bool = false
# "abc" < "def";;
- : bool = true
# 10 >= 13 ;;
- : bool = false
# compare [1;2] [1;2;3];;
- : int = -1
# "toto" = "toto";;
- : bool = true
# "toto" == "toto";;
- : bool = false
Voir le manuel
1.3. Fonctions sur les booléens

Les fonctions classiques sur les booléens sont disponibles:

not : bool -> bool non logique
(&&) : bool -> bool -> bool et logique
(||) : bool -> bool -> bool ou logique
1.4. Fonctions sur les entiers

On trouve les opérations arithmétiques habituelles sur les entiers:

(+), (-) : int -> int -> int addition, soustraction
( * ), (/), (mod) multiplication, division entière, modulo
~- : int -> int négation
succ, pred : int -> int successeur, prédécesseur
abs : int -> int valeur absolue

Les débordements (overflow) sont ignorés. Dans les cas où ils doivent être gérés, on pourra utiliser les constantes min_int et max_int qui sont respectivement les plus petit et plus grand entiers représentables sur la plateforme d'exécution. Voir le manuel

1.5. Opérations bit à bit

Des fonctions sont disponibles pour manipuler les entiers au niveau des bits, pour l'utilisation de masques, gestion de protocoles ou données binaires, ...

lnot : int -> int négation bit à bit logique
(lor), (land) : int -> int -> int ou et et bit à bit logique
(lxor) ou exclusif bit à bit logique
(lsl), (lsr), (asr) décalages à gauche et à droite

Voir le manuel

1.6. Fonctions sur les flottants

Il n'y a pas de surcharge en OCaml, c'est-à-dire que le même identifiant ne peut pas correspondre à deux fonctions différentes selon le type de ses arguments. Les opérations communes aux entiers et aux flottants (addition, ...) se distinguent donc par un '.' ajouté aux opérateurs flottants:

(+.), (-.) : float -> float -> float addition, soustraction
( *. ), (/.), mod_float multiplication, division, modulo
~-. : float -> float négation
abs_float : float -> float valeur absolue
sqrt : float -> float racine carrée
( ** ) : float -> float -> float puissance
float_of_int : int -> float création d'un flottant depuis un entier
truncate : float -> int création d'un entier à partir de la partie entière d'un flottant

Il existe des valeurs spéciales pour indiquer des valeurs qui ne sont pas des nombres ("not a number"): infinity, neg_infinity, ... Ces valeurs sont obtenues en effectuant certaines opérations comme une division par 0.0.

Beaucoup d'autres fonctions sont disponibles sur les flottants (trigonométrie, ...). Voir le manuel

1.7. Entrées/Sorties

Un certain nombre de fonctions d'entrées/sorties sont disponibles dans la bibliothèque préchargée. Certaines fonctions interagissent avec l'entrée standard, la sortie standard ou la sortie d'erreur, tandis que d'autres sont plus générales et agissent sur un canal (channel) passé en paramètre.

A l'aide de la documentation de Pervasives, faire les exercices suivants:

Exercice 1: Somme de deux entiers

Ecrire un programme qui affiche la somme de deux entiers saisis par l'utilisateur sur l'entrée standard, sous la forme "La somme de A et B est C." avec un retour chariot. On pourra lancer son programme par la commande

ocaml fichier.ml
Exercice 2: Implémenter cat

Ecrire un programme se comportant comme une simplification de la commande UNIX cat: il affichera sur sa sortie standard ce qu'il lit sur son entrée standard. On traitera proprement la détection de la fin de lecture.

2. Bibliothèque standard

La bibliothèque dite "standard" contient des modules disponibles sur toutes les plateformes sur lesquelles OCaml fonctionne. Elle est donc un socle stable pour développer des applications portables.

L'utilisation des éléments d'un module se fait en préfixant l'élément (fonction, type, ...) par le nom du module suivi d'un point, comme dans List.split ou String.lowercase. Il est également possible d'ouvrir le module, par l'instruction

open Nom_du_module;;

pour rendre tous ses éléments visibles après le open.

Attention, dans ce cas, les éléments du module ouvert masquent les éléments de même nom visibles auparavant:

# let length x = x + 1;;
val length : int -> int = <fun>
# length 10;;
- : int = 11
# open List;;
# length 10;;
Error: This expression has type int but an expression was expected of type
         'a list

En général, une bonne pratique est de ne pas ouvrir de module, mais de préfixer les éléments par leur nom complet afin de faciliter la lecture du programme. En effet, la présence du nom du module permet de plus facilement déduire quelle structure de données est manipulée, puisqu'il n'est pas nécessaire en OCaml d'indiquer explicitement les types. On peut faire une exception pour les constructeurs de type et les champs d'enregistrements, c'est-à-dire ouvrir un module pour ne pas avoir à préfixer les constructeurs et champs mais préfixer tout de même les autres éléments lors de leur utilisation.

On pourra également définir un alias de module avec la construction suivante:

# module L = List;;
module L = List

Dans le code qui suit cette construction, on peut faire référence au module L à la place du module List, ce dernier restant cependant accessible:

# L.length ;;
- : 'a list -> int = <fun>
# List.length ;;
- : 'a list -> int = <fun>

Enfin, il n'y a pas de hiérarchie dans cette bibliothèque, les modules disponibles sont tous au même niveau (même si certains modules en contiennent d'autres). Nous allons cependant les introduire par grandes catégories et détailler les possibilités de quelques uns d'entre eux.

2.1. Structures de données

La bibliothèque standard offre plusieurs modules permettant de construire et manipuler des structures de données courantes. Nous avons déjà vu précédemment les modules suivants:

  • Array, pour la manipulation des vecteurs,
  • Char, pour les opérations sur les caractères,
  • Uchar, pour manipuler des caractères Unicode (OCaml ≥ 4.03.0),
  • List, pour la manipulation des listes,
  • String, pour les manipulations des chaînes de caractères.

Le module Buffer permet de manipuler des tampons extensibles de caractères, dans un style impératif, plus efficaces dans certains algorithmes que des concaténations à répétition de chaînes de caractères.

On remarquera que les vecteurs et les listes sont génériques puisqu'ils permettent de manipuler des vecteurs et des listes d'éléments arbitraires (listes d'entiers, listes de chaînes, ...).

D'autres structures de données sont disponibles et offrent aussi cette généricité.

Exercice 3: Palindrome sur les chaînes de caractères

Ecrire une fonction is_palindrome prenant en paramètre une chaîne de caractères et retournant true si c'est un palindrome, false sinon. Une chaîne est un palindrome si elle est symétrique (e.g. "radar", "laval").

Exercice 4: Tac

Ecrire une fonction tac qui lit un fichier en paramètre et retourne la liste de ses lignes en ordre inverse.

Exemple d'appel:

# tac "/etc/shells";;
/usr/bin/zsh
/bin/zsh
/bin/rbash
/bin/bash
/bin/dash
/bin/sh
# /etc/shells: valid login shells
- : unit = ()
Exercice 5: Paramètres optionnels

On peut spécifier des paramètres optionnels pour les fonctions, avec ou sans valeur par défaut.

Dans let f ?x y = ..., x sera une valeur du type option. Le type option est prédéfini ainsi:

type 'a option = None | Some of 'a

et permet de manipuler des valeurs optionnelles. On pourra donc utiliser le filtrage pour procéder par cas sur la structure de x:

# let f ?x y = match x with None -> y | Some x -> x + y;;
val f : ?x:int -> int -> int = <fun>

Lors de l'appel d'une telle fonction, on pourra ou non préciser la valeur du paramètre optionnel:

# f 4;;
- : int = 4
# f ~x: 1 4;;
- : int = 5

Pour donner une valeur par défaut à un paramètre, on pourrait utiliser la forme suivante:

# let f ?x =
  let x = match x with None -> 1 | Some x -> x in
  fun y -> x + y;;
val f : ?x:int -> int -> int = <fun>

On préférera la syntaxe suivante, plus simple et lisible:

# let f ?(x=1) y = x + y;;
val f : ?x:int -> int -> int = <fun>

La façon de préciser la valeur de l'argument lors de l'application de f ne change pas:

# f 4;;
- : int = 5
# f ~x: 2 4;;
- : int = 6

Ecrire une fonction list_remove_doubles prenant en paramètre une liste et retournant la même liste mais dans laquelle chaque élément apparaît au plus une fois. La fonction acceptera en paramètre optionnel une fonction de comparaison pour savoir si deux éléments sont identiques. Par défaut ce paramètre sera la fonction d'égalité polymorphe (=).

# list_remove_doubles [ 1 ; 1 ; 3; 2 ; 2; 2 ; 1; 2; 3; 4];;
- : int list = [1; 3; 2; 4]
# let comp_string s1 s2 = String.lowercase s1 = String.lowercase s2;;
val comp_string : string -> string -> bool = <fun>
# list_remove_doubles ~pred: comp_string
  [ "hello"; "HELlo";"world";"heLLO"; "WorLD"; "!"; "!"; "hello"];;
- : string list = ["hello"; "world"; "!"]
Exercice 6: Découpage en liste de mots

Ecrire une fonction prenant une chaîne de caractères et retournant la liste des mots qu'elle contient. Un mot sera composé uniquement des caractères 'a' à 'z' et 'A' à 'Z'.

Note: Il est possible de filtrer un caractère selon un intervalle de caractères donné, grâce à la notation ..:

# match 'c' with
  'a'..'z'
| 'A'..'Z' -> "letter"
| '0'..'9' -> "number"
| _ -> "something else"
;;
- : string = "letter"
# words "maitre corbeau sur un arbre perche ... par l'odeur alleche...";;
- : string list =
["maitre"; "corbeau"; "sur"; "un"; "arbre"; "perche"; "par"; "l"; "odeur";
 "alleche"]
2.1.1. Tables de hachage

Le module Hashtbl permet d'utiliser des tables de hachage. Les tables de hachage sont des structures de données permettant de stocker des associations clé-valeur, avec modification en place. L'ajout d'une valeur pour une clé existante masque la valeur précédente associée à cette clé. Les clés et les valeurs peuvent être de n'importe quel type, tant que l'on sait comment comparer les clés (égalité) et que l'on a une fonction pour calculer un indice à partir d'une clé (fonction de hachage). En effet, les valeurs ne sont pas stockées dans un tableau de taille infinie, mais au contraire dans un tableau de taille finie, et la fonction de hachage doit permettre une répartition la plus homogène possible dans le tableau, afin d'avoir des temps d'accès les plus courts. Si deux clés ont la même valeur de hachage, les deux associations clé-valeur sont stockées dans une liste à l'indice commun du tableau. La taille de la table doit donc être adaptée au nombre d'associations à stocker. Si elle est trop petite, les conflits dans les hash de clés seront nombreux et au final on parcourra souvent les listes d'associations ayant la même valeur de hachage. Si elle est trop grande, elle prend inutilement de la place. Enfin, on conseille de prendre un nombre premier comme taille de la table.

Un type de table de hachage générique est disponible, utilisant des fonctions d'égalité et de hachage prédéfinies. Il est également possible de définir son propre type de table de hachage spécialisé pour un type de données. Pour cela, on définit un module contenant les fonctions d'égalité et de hachage et on passe ce module à un autre module (Hashtbl.Make), afin de construire un nouveau module permettant la manipulation de tables de hachage spécialisées par les fonctions du module en paramètre. Les modules prenant d'autres modules en paramètres sont appelés foncteurs. Ils sont exposés dans la section Foncteurs et réutilisabilité.

Voici un exemple d'utilisation de table de hachage:

# type person = { name : string ; firstname : string };;
type person = { name : string; firstname : string; }
# let users = Hashtbl.create 101;;
val users : ('_a, '_b) Hashtbl.t = <abstr>
# let robert = { name = "Bidochon" ; firstname = "Robert"};;
val robert : person = {name = "Bidochon"; firstname = "Robert"}
# Hashtbl.add users "bidochon" robert;;
- : unit = ()
# Hashtbl.find users "bidochon";;
- : person = {name = "Bidochon"; firstname = "Robert"}
# let raymonde = { name = "Bidochon" ; firstname = "Raymonde"};;
val raymonde : person = {name = "Bidochon"; firstname = "Raymonde"}
# Hashtbl.add users "bidochon" raymonde (* robert est masqué *);;
- : unit = ()
# Hashtbl.find users "bidochon";;
- : person = {name = "Bidochon"; firstname = "Raymonde"}
# Hashtbl.remove users "bidochon" (* robert est démasqué *);;
- : unit = ()
# Hashtbl.find users "bidochon";;
- : person = {name = "Bidochon"; firstname = "Robert"}
# Hashtbl.remove users "bidochon" (* robert est supprimé *);;
- : unit = ()
# Hashtbl.find users "bidochon";;
Exception: Not_found.

A sa création, la table users a le type ('_a, '_b) Hashtbl.t: users est du type Hashtbl.t mais les deux paramètres de type ne sont pas encore fixés (on a `_a au lieu d'un `a qui indiquerait le polymorphisme), ils le seront dès que la suite du programme mettra des contraintes sur ces types. On peut d'ailleurs le vérifier maintenant que la table a été utilisée:

# users;;
- : (string, person) Hashtbl.t = <abstr>

L'indication = <abstr> signifie que le type est abstrait. C'est le cas de beaucoup de structures de données prédéfinies: leur représentation interne est masquée et il faut donc obligatoirement passer par les fonctions fournies pour manipuler ces structures. Cet aspect sera exposé dans la partie sur les modules dans le chapitre Programmation modulaire.

Exercice 7: Comptage de mots

Ecrire un programme prenant en paramètre un nom de fichier et comptant le nombre d'occurrences de chaque mot apparaissant dans le fichier. A la fin, le programme affiche chaque mot avec son nombre d'apparitions. On pourra utiliser la fonction words définie dans l' Exercice 6.

2.1.2. Dictionnaires

Ces structures de données permettent d'associer des valeurs à des clés, comme pour les tables de hachage mais dans un style applicatif, sans effet de bord. Elles sont basées sur des arbres binaires et nécessitent pour cela de savoir comment ordonner les clés des paires (clé, valeur) à ranger.

On crée un module pour gérer ce genre de structure en utilisant le foncteur Map.Make, du module Map. On peut utiliser la fonction générique Pervasives.compare comme fonction d'ordre, notamment quand on utilise comme clé un type de données prédéfini pour lequel cette fonction a le comportement souhaité:

# module MyKey = struct
  type t = int
  let compare = Pervasives.compare
end;;
module MyKey : sig type t = int val compare : 'a -> 'a -> int end
# module MyMap = Map.Make(MyKey);;
module MyMap :
  sig
    type key = MyKey.t
    type 'a t = 'a Map.Make(MyKey).t
    val empty : 'a t
    val is_empty : 'a t -> bool
    val mem : key -> 'a t -> bool
    val add : key -> 'a -> 'a t -> 'a t
    val singleton : key -> 'a -> 'a t
    val remove : key -> 'a t -> 'a t
    val merge :
      (key -> 'a option -> 'b option -> 'c option) -> 'a t -> 'b t -> 'c t
    val union : (key -> 'a -> 'a -> 'a option) -> 'a t -> 'a t -> 'a t
    val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int
    val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool
    val iter : (key -> 'a -> unit) -> 'a t -> unit
    val fold : (key -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
    val for_all : (key -> 'a -> bool) -> 'a t -> bool
    val exists : (key -> 'a -> bool) -> 'a t -> bool
    val filter : (key -> 'a -> bool) -> 'a t -> 'a t
    val partition : (key -> 'a -> bool) -> 'a t -> 'a t * 'a t
    val cardinal : 'a t -> int
    val bindings : 'a t -> (key * 'a) list
    val min_binding : 'a t -> key * 'a
    val max_binding : 'a t -> key * 'a
    val choose : 'a t -> key * 'a
    val split : key -> 'a t -> 'a t * 'a option * 'a t
    val find : key -> 'a t -> 'a
    val map : ('a -> 'b) -> 'a t -> 'b t
    val mapi : (key -> 'a -> 'b) -> 'a t -> 'b t
  end
# let map = MyMap.empty;;
val map : 'a MyMap.t = <abstr>
# let map = MyMap.add 1 "coucou" map;;
val map : string MyMap.t = <abstr>
# let map = MyMap.add 2 "bonjour" map;;
val map : string MyMap.t = <abstr>
# let map = MyMap.add 3 42 map;;
Error: This expression has type string MyMap.t = string Map.Make(MyKey).t
       but an expression was expected of type
         int MyMap.t = int Map.Make(MyKey).t
       Type string is not compatible with type int
# MyMap.iter
  (fun key value -> Printf.printf "cle=%d, valeur=%s\n" key value)
  map;;
- : unit = ()
# flush stdout;;
cle=1, valeur=coucou
cle=2, valeur=bonjour
- : unit = ()
Exercice 8: Comptage de mots avec un dictionnaire

Reprendre l' Exercice 7 en utilisant un dictionnaire.

2.1.3. Ensembles

Le module Set permet de définir des structures de données représentant des ensembles de valeurs d'un même type. Il est alors possible d'obtenir le plus grand élément, le plus petit, de faire des unions, intersections, ....

Comme pour les dictionnaires, on crée un module de gestion d'ensembles à l'aide d'un foncteur, Set.Make. Le module passé en paramètre doit définir le type des éléments et la fonction d'ordre sur ces éléments. On utilise dans l'exemple ci-dessous le même module MyKey que dans l'exemple des dictionnaires, pour faire un module de manipulation d'ensembles d'entiers:

# Random.self_init();;
- : unit = ()
# module IntSet = Set.Make(MyKey);;
module IntSet :
  sig
    type elt = MyKey.t
    type t = Set.Make(MyKey).t
    val empty : t
    val is_empty : t -> bool
    val mem : elt -> t -> bool
    val add : elt -> t -> t
    val singleton : elt -> t
    val remove : elt -> t -> t
    val union : t -> t -> t
    val inter : t -> t -> t
    val diff : t -> t -> t
    val compare : t -> t -> int
    val equal : t -> t -> bool
    val subset : t -> t -> bool
    val iter : (elt -> unit) -> t -> unit
    val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
    val for_all : (elt -> bool) -> t -> bool
    val exists : (elt -> bool) -> t -> bool
    val filter : (elt -> bool) -> t -> t
    val partition : (elt -> bool) -> t -> t * t
    val cardinal : t -> int
    val elements : t -> elt list
    val min_elt : t -> elt
    val max_elt : t -> elt
    val choose : t -> elt
    val split : elt -> t -> t * bool * t
    val find : elt -> t -> elt
    val of_list : elt list -> t
  end
# let rec f set i =
    if i <= 100 then
      f (IntSet.add (Random.int 4000) set) (i+1)
    else
      set;;
val f : IntSet.t -> int -> IntSet.t = <fun>
# let set = f IntSet.empty 1;;
val set : IntSet.t = <abstr>
# Printf.printf "Plus petit élément tiré: %d, plus grand: %d\n"
  (IntSet.min_elt set) (IntSet.max_elt set);;
- : unit = ()
# flush stdout;;
Plus petit élément tiré: 24, plus grand: 3918
- : unit = ()
# let sum = IntSet.fold (+) set 0;;
val sum : int = 153092
Exercice 9: Mots en commun

Ecrire un programme prenant deux fichiers et affichant la liste des mots en commun sur la sortie standard. On utilisera encore la fonction words de l' Exercice 6.

2.1.4. Files d'attente

Le module Queue implémente les files d'attente (ou FIFO, pour First In First Out). Le type Queue.t des files est donc paramétré par le type des éléments dans la file, de la même façon que le type list. Les modifications des files sont faites en place (par effet de bord).

2.1.5. Piles

Le module Stack implémente les piles, là encore avec un type Stack.t paramétré par le type des éléments stockés dans la pile. Les modifications sont faites en place.

2.1.6. Remarques

On peut remarquer que certaines conventions de nommage sont respectées dans la bibliothèque standard. Ainsi, les modules définissant un type de données le nomment t, et les fonctions similaires partagent le même nom:

  • String.length, Array.length, List.length, etc.
  • List.fold_left, Array.fold_left, Hashtbl.fold, etc.

L'ordre des arguments est aussi cohérent. Ces conventions facilitent l'utilisation d'un module à la place d'un autre pour changer de structure de données. Nous verrons un exemple dans la section sur les foncteurs (section Foncteurs et réutilisabilité).

2.2. Calcul

Le module Complex définit un type de données pour les nombres complexes et des fonctions pour les manipuler.

Différents modules permettent d'effectuer des calculs sur des entiers avec des représentations différentes du type int:

  • Int32: entiers 32 bits,
  • Int64: entiers 64 bits,
  • Nativeint: entiers 32 ou 64 bits, selon l'architecture,
  • Big_int: entiers de taille arbitraire.
2.3. Entrées-sorties

Le module Printf donne accès aux fonctions équivalentes des fonctions printf, sprintf, fprintf, etc. du langage C. Par exemple:

# let str = Printf.sprintf "La somme de %d et %d est %d" 1 2 (1+2);;
val str : string = "La somme de 1 et 2 est 3"
# let buffer = Buffer.create 256;;
val buffer : Buffer.t = <abstr>
# Printf.bprintf buffer "Le produit de %d et %d donne %d" 4 5 (4*5);;
- : unit = ()
# Buffer.contents buffer;;
- : string = "Le produit de 4 et 5 donne 20"

Le type des paramètres des fonctions du module Printf est spécial, pour permettre le passage d'un nombre variable de paramètres en fonction de la chaîne de format donnée:

# Printf.printf;;
- : ('a, out_channel, unit) format -> 'a = <fun>

Comme en C, des fonctions de lecture de valeurs de différents types depuis des chaînes sont disponibles, dans le module Scanf. Dans l'exemple suivant, la fonction en paramètre de sscanf prend deux arguments entiers car la chaîne de format contient deux %d. La cohérence de type entre la chaîne de format et la fonction est réalisée par le compilateur:

# let str = "1 + 2";;
val str : string = "1 + 2"
# let sum = Scanf.sscanf str "%d + %d" (fun a b -> a + b) ;;
val sum : int = 3

Des fonctionnalités de formatage de sortie texte plus élaborées sont disponibles dans le module Format: définition de marges, retour à la ligne automatique en cas de dépassement de la largeur maximum imposée, manipulation de "boîtes" de pretty-print, ....

Enfin, l'échange, le stockage et la lecture de données peuvent se faire en utilisant les fonctions génériques de sérialisation du module Marshal. Les données ne doivent cependant pas contenir de fonctions.

2.4. Interface avec le système
2.4.1. Analyse de la ligne de commande

Le module Arg offre des facilités pour analyser la ligne de commande de lancement du programme.

Exercice 10: Somme de deux entiers

Ecrire un programme qui accepte deux entiers sur la ligne de commande ainsi qu'une option -prod. Le programme affiche la somme des deux entiers, ou bien le produit si l'option -prod est passée. On écrira le code dans un fichier exercice_arg.ml et on l'exécutera par exemple par la commande

ocaml exercice_arg.ml -prod 4 8
32
2.4.2. Noms de fichiers

Le module Filename contient des fonctions de manipulation des noms de fichiers, utiles notamment pour produire du code portable sur UNIX/Linux/Mac OS X et Windows en fournissant les fonctions current_dir_name, parent_dir_name, concat, dirname, .... Par exemple:

# let file = "/home/foo/file.txt";;
val file : string = "/home/foo/file.txt"
# let dir = Filename.dirname file;;
val dir : string = "/home/foo"
# let bname = Filename.basename file;;
val bname : string = "file.txt"
# Filename.concat dir bname;;
- : string = "/home/foo/file.txt"
# Filename.check_suffix file "txt";;
- : bool = true
2.4.3. Gestion des exceptions

Le module Printexc offre des fonctions de convenance pour traiter et afficher les exceptions, comme par exemple Printexc.to_string qui retourne une chaîne pour représenter l'exception ou Printexc.print qui affiche une exception non rattrapée lors de l'application d'une fonction et relève l'exception:

# int_of_string "bouh!";;
Exception: Failure "int_of_string".
# Printexc.print int_of_string "bouh!";;
Uncaught exception: Failure("int_of_string")
Exception: Failure "int_of_string".
2.4.4. Contrôle du ramasse-miettes

Le module Gc permet de modifier le comportement du ramasse-miettes ou glaneur de cellules (garbage collector en anglais) et d'obtenir diverses statistiques sur la gestion de la mémoire. Il est également possible d'indiquer un traitement à effectuer lorsque la mémoire occupée par une valeur est libérée par le GC (lorsque cette valeur n'est plus accessible).

2.4.5. Interface avec le système d'exploitation

Le module Sys offre des fonctions portables de communication avec le système d'exploitation concernant le système de fichiers, le répertoire courant, les signaux, certaines constantes comme la taille des mots, la longueur maximale d'une chaîne de caractères, etc. On trouve également dans ce module Sys.argv qui est le tableau des arguments de la ligne de commande. Comme en C, Sys.argv.(0) est le nom de la commande lancée.

# let old_dir = Sys.getcwd();;
val old_dir : string = "/home/guesdon/devel/form-ocaml/codes/stdlib"
# print_endline old_dir;;
/home/guesdon/devel/form-ocaml/codes/stdlib
- : unit = ()
# Sys.chdir "/tmp";;
- : unit = ()
# print_endline (Sys.getcwd());;
/tmp
- : unit = ()
# Sys.command "ls | head -n 3";;
claws-mail-1000
clementine-art-g10422.jpg
clementine-art-H10422.jpg
- : int = 0
# Sys.chdir old_dir;;
- : unit = ()
2.5. Utilitaires

Quelques autres modules sont indiqués ici à titre informatif:

  • Digest offre des fonctions de calcul de hash utilisant l'algorithme MD5:
    # let md5 = Digest.string (Marshal.to_string robert []);;
    val md5 : Digest.t = "\244\233Sv\028U\166\167\230q\153\183\1357\207r"
    # Digest.to_hex md5;;
    - : string = "f4e953761c55a6a7e67199b78737cf72"
  • Lazy permet d'utiliser de l'évaluation paresseuse, en reportant l'évaluation d'une expression au moment où elle sera nécessaire:
    # let f a b () = print_endline "évaluation!"; a + b ;;
    val f : int -> int -> unit -> int = <fun>
    # let res = Lazy.from_fun (f 1 2);;
    val res : int Lazy.t = <lazy>
    # Lazy.force res;;
    évaluation!
    - : int = 3
  • Lexing et Parsing servent lorsqu'on utilise les outils ocamllex et ocamlyacc (cf. 8),
  • Random permet la génération pseudo-aléatoire de nombres:
    # Random.self_init()
     (* initialisation en utilisant une source aléatoire de la machine *);;
    - : unit = ()
    # Random.int 10000 (* un nombre aléatoire entre 0 et 9999 *) ;;
    - : int = 9359
    # Random.int 10000 ;;
    - : int = 9866
    # Random.float 1000. ;;
    - : float = 778.679209787576724
3. Autres bibliothèques de la distribution

La distribution contient encore d'autres bibliothèques, dont certaines disponibles selon le système d'exploitation et les bibliothèques installées:

  • Bigarray permet la manipulation de tableaux de grande taille,
  • Dynlink permet le chargement dynamique de code, utile pour la création d'architecture à base de greffons (plugins),
  • Graphics offre des fonctions pour créer des graphiques simples,
  • Labltk est l'interface avec la bibliothèque graphique Tcl/Tk, pour créer des interfaces utilisateur graphiques simples (par le module Tk et les autres modules du répertoire labltk de l'installation),
  • Num est une bibliothèque de calcul arithmétique en précision arbitraire, avec des modules comme Big_int,
  • Str permet l'utilisation d'expressions régulières (recherche, substitution, ...),
  • Threads permet la création de programme multi-threads, grâce aux modules Thread, Mutex, Condition et Event,
  • Unix offre l'accès aux fonctions systèmes UNIX.

Lorsqu'on utilise ces bibliothèques dans un programme OCaml, il faut les ajouter sur la ligne de commande de création de l'exécutable, à la façon des bibliothèques libXXX.a en C. Une bibliothèque B dépendant d'une bibliothèque A doit être placée après la bibliothèque dont elle dépend:

ocamlc -o mon_exe a.cma b.cma module1.cmo ...

pour la compilation code-octet, ou

ocamlopt -o mon_exe a.cmxa b.cmxa module1.cmx ...

pour la compilation en code natif.

Exercice 11: Lister les fichiers de /tmp

Ecrire un programme qui affiche la liste des fichiers du répertoire /tmp, en utilisant la bibliothèque Unix. On compilera son programme par la commande

ocamlc -o lstmp unix.cma lstmp.ml

(si votre programme est dans le fichier lstmp.ml).

Exercice 12: Implémentation de printenv

Ecrire un programme qui simule la commande printenv, c'est-à-dire qui affiche la liste des variables d'environnement et leur contenu sous la forme VAR=VALUE. Si des arguments sont passés sur la ligne de commande, seul le contenu des variables données est affiché. On utilisera la bibliothèque Unix.

On testera le programme avec les commandes suivantes:

  • Affichage de tout l'environnement:
    $ ./myprintenv
  • Affichage des variables SHELL et TERM:
    ./myprintenv SHELL TERM
    /bin/bash
    xterm-256color
    
  • Test d'erreur:
    ./myprintenv FOO ; echo $?
    1