Précédent Index Suivant

Autres bibliothèques de la distribution

Les autres bibliothèques fournies avec la distribution du langage Objective CAML portent sur les extensions suivantes : 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 :
e = lim
 
m -> ¥
æ
ç
ç
è
1 +
1

m
ö
÷
÷
ø
m



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






Précédent Index Suivant