Autres bibliothèques de la distribution
Les autres bibliothèques fournies avec la distribution du langage
Objective CAML portent sur les extensions suivantes :
-
graphisme, avec le module Graphics, portable,
qui a été décrit au chapitre 5 ;
- arithmétique exacte, comportant plusieurs modules, et permettant
d'effectuer des calculs exacts sur les entiers et les rationnels. La
représentation des nombres utilise les entiers d'Objective CAML quand
cela est possible ;
- filtrage d'expressions régulières, permettant des
manipulations de chaînes et de textes plus aisées. Le module
Str sera décrit au chapitre 11 ;
- appels système Unix, avec le module Unix qui
permet d'effectuer depuis Objective CAML des appels système Unix. Une grande
partie de cette bibliothèque est néanmoins compatible avec
Windows. Cette bibliothèque sera utilisée aux chapitres
18 et 20 ;
- processus légers, comportant plusieurs modules qui seront
largement décrits et utilisés au chapitre 19 ;
- accès aux bases de données NDBD, ne fonctionnant que sous Unix
et qui ne sera pas décrit ;
- chargement dynamique de code-octet, constituée du seul
module Dynlink.
On décrira par l'usage les bibliothèques sur les grands nombres et le
chargement dynamique.
Arithmétique exacte
La bibliothèque des grands nombres offre une arithmétique exacte sur
les entiers et les rationnels. Les valeurs de type int et
float connaissent deux limitations : les calculs sur les
entiers sont faits modulo le plus grand entier positif, ce qui
peut provoquer des débordements passant inaperçus ; les
résultats de calculs sur les flottants sont arrondis, ce qui, par
propagation peut conduire à des erreurs. La bibliothèque
présentée ici pallie ces défauts.
Cette bibliothèque est écrite en partie en C. Pour cette raison, il faudra
construire une boucle d'interaction incluant ce code en utilisant la
commande :
ocamlmktop -custom -o top nums.cma -cclib -lnums
Elle est constituée de plusieurs modules. Les deux plus importants
sont Num pour toutes les opérations et
Arith_status pour le contrôle des options des calculs.
Le type général num est un type somme regroupant trois
types de base :
type num = Int of int
| Big_int of big_int
| Ratio of ratio
Les types big_int et ratio sont abstraits.
Les opérations sur les valeurs de type num sont suivies du
symbole /. Par exemple l'addition de deux num s'écrira
+/ et sera de type num -> num -> num. Il en sera de
même pour les comparaisons. Voici un premier exemple qui calcule la
factorielle :
# let rec fact_num n =
if Num.(<=/) n (Num.Int 0) then (Num.Int 1)
else Num.( */ ) n (fact_num ( Num.(-/) n (Num.Int 1)));;
val fact_num : Num.num -> Num.num = <fun>
# let r = fact_num (Num.Int 100);;
val r : Num.num = Num.Big_int <abstr>
# let n = Num.string_of_num r in (String.sub n 0 50) ^ "..." ;;
- : string = "93326215443944152681699238856266700490715968264381..."
L'ouverture du module Num rend le code de fact_num
plus facile à lire :
# open Num ;;
# let rec fact_num n =
if n <=/ (Int 0) then (Int 1)
else n */ (fact_num ( n -/ (Int 1))) ;;
val fact_num : Num.num -> Num.num = <fun>
Les calculs sur les rationnels sont eux aussi exacts. Si on cherche à
calculer le nombre e en suivant la définition suivante :
On écrira une fonction qui calcule cette limite jusqu'à un certain m.
# let calc_e m =
let a = Num.(+/) (Num.Int 1) ( Num.(//) (Num.Int 1) m) in
Num.( **/ ) a m;;
val calc_e : Num.num -> Num.num = <fun>
# let r = calc_e (Num.Int 100);;
val r : Num.num = Ratio <abstr>
# let n = Num.string_of_num r in (String.sub n 0 50) ^ "..." ;;
- : string = "27048138294215260932671947108075308336779383827810..."
Le module Arith_status permet de contrôler une partie des
calculs comme la normalisation des rationnels, l'approximation pour
affichage et le traitement des dénominateurs nuls. La fonction
arith_status affiche l'état de ces indicateurs.
# Arith_status.arith_status();;
Normalization during computation --> OFF
(returned by get_normalize_ratio ())
(modifiable with set_normalize_ratio <your choice>)
Normalization when printing --> ON
(returned by get_normalize_ratio_when_printing ())
(modifiable with set_normalize_ratio_when_printing <your choice>)
Floating point approximation when printing rational numbers --> OFF
(returned by get_approx_printing ())
(modifiable with set_approx_printing <your choice>)
Error when a rational denominator is null --> ON
(returned by get_error_when_null_denominator ())
(modifiable with set_error_when_null_denominator <your choice>)
- : unit = ()
Ils pourront être modifiés selon les besoins d'un calcul. Par exemple
si on demande l'affichage de la valeur approchée des rationnels, on
obtiendra, pour le calcul précédent :
# Arith_status.set_approx_printing true;;
- : unit = ()
# Num.string_of_num (calc_e (Num.Int 100));;
- : string = "0.270481382942e1"
Les calculs avec les grands nombres sont plus longs qu'avec les
entiers et les valeurs occupent plus d'espace mémoire. Néanmoins,
cette bibliothèque essaie de conserver les représentations les plus
économiques. De toute façon, éviter la propagation
d'erreur d'arrondis et pouvoir calculer sur des grands nombres,
justifient une perte d'efficacité.
Chargement dynamique de code
Le module Dynlink offre la possibilité de charger
dynamiquement des programmes sous la forme de code-octet. Le
chargement dynamique de code procure les avantages suivants :
-
réduire la taille en code d'un programme. Si certains modules ne
sont pas utilisés, ils ne seront pas chargés.
- pouvoir choisir à l'exécution le module à charger. Selon
certaines conditions d'exécution on choisit un module plutôt qu'un
autre à charger.
- pouvoir modifier le comportement d'un module en cours d'exécution.
Là aussi selon certaines conditions le programme peut charger un
nouveau module et masquer le code du précédent.
La boucle d'interaction d'Objective CAML utilise déjà un tel mécanisme. Il est
pratique que le programmeur puisse y avoir accès.
Lors du chargement d'un fichier objet (d'extension .cmo), les
différentes expressions sont évaluées. Le programme principal, qui a
demandé le chargement dynamique du code, n'a pas accès aux noms des
déclarations. Donc c'est au module chargé dynamiquement de mettre à
jour une table de fonctions utilisées par le programme principal.
Warning
Le chargement dynamique de code ne fonctionne que pour les fichiers
objets en code-octet.
Description du module
Le chargement dynamique d'un fichier de code-octet f.cmo
nécessite d'une part de connaître les chemins de recherche où aller
trouver ce fichier et d'autre part les noms des modules dont il se
sert. Par défaut, les fichiers de code-octet chargés dynamiquement
n'ont pas accès aux chemins et aux modules des bibliothèques de la
distribution. Il faut alors ajouter ce chemin et le nom des modules
nécessaires au chargement dynamique d'un module.
| init |
: |
unit -> unit |
| |
|
initialise le chargement dynamique |
| add_interfaces |
: |
string list -> string list -> unit |
| |
|
ajoute des noms de modules et des chemins
de recherche pour le chargement |
| loadfile |
: |
string -> unit |
| |
|
charge un fichier code-octet |
| clear_avalaible_units |
: |
unit -> unit |
| |
|
met à vide les noms de modules chargeables
et les chemins de recherche |
| add_avalaible_units |
: |
(string * Digest.t) list -> unit |
| |
|
ajoute un nom de module et une empreinte pour le
chargement sans avoir besoin du fichier interface |
| allow_unsafe_modules |
: |
bool -> unit |
| |
|
accepte de charger des fichiers
contenant des déclarations
external |
| loadfile_private |
: |
string -> unit |
| |
|
le module chargé n'est pas accessible
aux prochains modules chargés |
| L'empreinte d'une interface .cmi
peut être obtenue par la commande extract_crc qui se trouve
dans le catalogue des bibliothèques de la distribution. |
Figure 8.10 : Fonctions du module Dynlink
De nombreuses erreurs peuvent survenir lors d'une demande de chargement
d'un module. Non seulement le fichier doit exister avec la bonne
interface dans un des chemins de recherche, mais de plus, le
code-octet doit être correct et chargeable. Ces erreurs sont
regroupées dans un type error utilisé comme argument
de l'exception Error et de la fonction error de type
error -> string qui permet de convertir une erreur en
description claire.
Exemple
Pour écrire un petit programme qui nous permettra d'illuster le
chargement dynamique de code-octet, on se donne trois modules :
-
F qui contient la définition d'une référence sur une
fonction f ;
- Mod1 et Mod2 qui modifient de façon
différente la fonction référencée par F.f.
Le module F est définit par le fichier f.ml :
let g () =
print_string "Je suis la fonction 'f' par défaut\n" ; flush stdout ;;
let f = ref g ;;
Le module Mod1 est définit dans le fichier mod1.ml :
print_string "Le module 'Mod1' modifie la valeur de 'F.f'\n" ; flush stdout ;;
let g () =
print_string "Je suis la fonction 'f' du module 'Mod1'\n" ;
flush stdout ;;
F.f := g ;;
Le module Mod2 est définit dans le fichier mod2.ml :
print_string "Le module 'Mod2' modifie la valeur de 'F.f'\n" ; flush stdout ;;
let g () =
print_string "Je suis la fonction 'f' du module 'Mod2'\n" ;
flush stdout ;;
F.f := g ;;
On définit enfin, dans le fichier main.ml, un programme
principal qui appelle la fonction référencée par F.f
originale, charge le module Mod1 et réappelle F.f,
puis charge le module Mod2 et appelle une dernière fois la
fonction F.f :
let main () =
try
Dynlink.init () ;
Dynlink.add_interfaces [ "Pervasives"; "F" ; "Mod1" ; "Mod2" ]
[ Sys.getcwd() ; "/usr/local/lib/ocaml/" ] ;
!(F.f) () ;
Dynlink.loadfile "mod1.cmo" ; !(F.f) () ;
Dynlink.loadfile "mod2.cmo" ; !(F.f) ()
with
Dynlink.Error e -> print_endline (Dynlink.error_message e) ; exit 1 ;;
main () ;;
Le programme principal doit, outre initialiser le chargement
dynamique, déclarer par un appel à Dynlink.add_interfaces
les interface utilisées.
On compile l'ensemble de ces modules :
$ ocamlc -c f.ml
$ ocamlc -o main dynlink.cma f.cmo main.ml
$ ocamlc -c f.cmo mod1.ml
$ ocamlc -c f.cmo mod2.ml
Si l'on exécute le programme main, on obtient :
$ main
Je suis la fonction 'f' par défaut
Le module 'Mod1' modifie la valeur de 'F.f'
Je suis la fonction 'f' du module 'Mod1'
Le module 'Mod2' modifie la valeur de 'F.f'
Je suis la fonction 'f' du module 'Mod2'
Au chargement dynamique d'un module, son code est exécuté. C'est
ce que manifeste, dans notre exemple, les affichages commençant
par Le module .... Les éventuels effets de bord qu'il contient
sont donc répercutés au niveau du programme qui a effectué ce
chargement. C'est pourquoi, les différents appels à F.f
appellent des fonctions différentes.
La bibliothèque Dynlink offre le mécanisme de base pour le
chargement dynamique de code-octet. Il reste au programmeur à
gérer des tables pour que ce chargement ait véritablement un effet.