Author: Christian.Queinnec@lip6.fr

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

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 :

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