Author: Christian.Queinnec@lip6.fr

## Calculatrice de bureau

Pour comprendre comment se bâtit un programme en Objective CAML, il est nécessaire d'en réaliser un. L'exemple choisi est une calculatrice de bureau, le modèle le plus simple, c'est-à-dire qui ne fonctionne que sur des nombres entiers et ne réalise que les quatre opérations standard de l'arithmétique.
Pour commencer, on définit le type touche pour représenter les touches d'une calculette. Celle-ci possède quinze touches, à savoir : une pour chaque opération, une pour chaque chiffre et la touche `=`.

    # type touche = Plus | Moins | Fois | Par | Egal | Chiffre of int ;;

On remarque que les touches numériques sont regroupés sous un unique constructeur Chiffre prenant un entier comme argument. De ce fait, certaines valeurs de type touche ne représentent pas réellement une touche. Par exemple, `(`Chiffre`` `3` `2``)` est une valeur possible du type touche, mais ne représente aucune touche de la calculatrice.
On écrit donc une fonction `valide` qui vérifie que son argument correspond à une touche de la calculatrice. Le type de cette fonction est touche -> bool, c'est-à-dire qu'elle prend comme argument une valeur de type touche et rend une valeur de type bool.
La première étape est de définir une fonction qui vérifie qu'un entier est compris entre 0 et 9. Nous déclarons cette fonction sous le nom `est_chiffre` :

    # let est_chiffre = function x -> (x>=0) && (x<=9) ;;
    val est_chiffre : int -> bool = <fun>

On définit ensuite la fonction `valide` par filtrage sur son argument de type touche :

    #   let valide tch = match tch with
         Chiffre n -> est_chiffre n
       | _ -> true ;;
    val valide : touche -> bool = <fun>

Le premier filtre s'applique quand l'argument de `valide` est une valeur construite avec le constructeur Chiffre; dans ce cas, l'argument de Chiffre est testé par la fonction `est_chiffre`. Le second filtre s'applique dans tous les autre cas de valeur du type touche. On rappelle que grâce au typage, la valeur filtrée est obligatoirement de type touche.
Avant d'entamer le codage du mécanisme de la calculatrice, nous allons déterminer un modèle permettant de décrire d'un point de vue formel la réaction à l'activation d'une des touches de la machine. On considère qu'une calculette possède quatre mémoires où sont respectivement conservés le dernier calcul effectué, la dernière touche actionnée, le dernier opérateur actionné et le nombre affiché à l'écran. L'ensemble de ces quatre mémoires est appelé l'état de la calculatrice, il est modifié par chaque appui sur une touche du clavier. Cette modification s'appelle une transition et la théorie gouvernant ce genre de mécanismes est celle des automates. Un état sera représenté dans notre programme par un type enregistrement :

    # type etat = {
       dce : int;    (* dernier calcul effectué     *)
       dta : touche; (* dernière touche actionnée   *)
       doa : touche; (* dernier opérateur actionné  *)
       vaf : int     (* valeur affichée             *)
     } ;;

La figure 2.6 donne un exemple d'une suite de transitions.

> | état | touche
> | (0,=,=,0) | 3
> ¾® | (0,3,=,3) | +
> ¾® | (3,+,+,3) | 2
> ¾® | (3,2,+,2) | 1
> ¾® | (3,1,+,21) | ×
> ¾® | (24,*,*,24) | 2
> ¾® | (24,2,*,2) | =
> ¾® | (48,=,=,48) |

> Figure 2.6 : transitions pour 3+21*2=

On a besoin par la suite d'une fonction `evalue` qui prend deux entiers et une valeur de type `touche` contenant un opérateur et qui renvoie le résultat de l'opération correspondant à la touche appliquée aux deux entiers. Cette fonction est définie par filtrage sur son dernier argument, de type touche :

    # let evalue x y tch = match tch with
         Plus  -> x + y
       | Moins -> x - y
       | Fois  -> x * y
       | Par   -> x / y
       | Egal  -> y
       | Chiffre _ -> failwith "evalue : no op";;
    val evalue : int -> int -> touche -> int = <fun>

On donne maintenant la définition de la fonction de transition en énumérant tous les cas possibles. On suppose que l'état courant est le quadruplet (a,b,Å,d) :

- une touche avec le chiffre x est enfoncée, alors deux cas sont à envisager :
  - la dernière touche enfoncée était aussi un chiffre. C'est donc un nombre que l'utilisateur de la calculette est en train de composer, il faut en conséquence ajouter le chiffre x à la valeur affichée, c'est à dire le remplacer par d×10+x. Le nouvel état est :
    (a,`(Chiffre x)`,Å,d×10+x)
  - la dernière touche enfoncée n'était pas un chiffre. C'est donc le début d'un nouveau nombre qui est composé. Le nouvel état est :
    (a,`(Chiffre x)`,Å,x)
- une touche avec l'opérateur Ä a été enfoncée, le deuxième opérande d'une opération a donc été entièrement composé et il s'agit pour la calculatrice d'effectuer cette opération. C'est à cette fin qu'est conservée la dernière opération (ici Å). Le nouvel état est :
  (aÅ d,Ä,Ä,a Å d)

Pour écrire la fonction `transition`, il suffit de traduire mot à mot en Objective CAML la définition précédente : la définition par cas devient une définition par filtrage sur la touche passée en argument. Le cas d'une touche qui comporte lui même deux cas, est traité par la fonction locale `transition_chiffre` par filtrage sur la dernière touche actionnée.

    # let transition et tou =
       let transition_chiffre n = function
           Chiffre _  ->  { et with dta=tou; vaf=et.vaf*10+n }
         |  _         ->  { et with dta=tou; vaf=n }
       in
          match tou with
              Chiffre p  ->  transition_chiffre p et.dta
            |  _         ->  let res = evalue et.dce et.vaf et.doa
                             in { dce=res; dta=tou; doa=tou; vaf=res }  ;;
    val transition : etat -> touche -> etat = <fun>

Cette fonction prend un etat, une touche et calcule le nouvel etat.
On peut maintenant tester ce programme avec l'exemple précédent :

    # let etat_initial = { dce=0; dta=Egal; doa=Egal; vaf=0 } ;;
    val etat_initial : etat = {dce=0; dta=Egal; doa=Egal; vaf=0}
    # let etat2 = transition etat_initial (Chiffre 3) ;;
    val etat2 : etat = {dce=0; dta=Chiffre 3; doa=Egal; vaf=3}
    # let etat3 = transition etat2 Plus ;;
    val etat3 : etat = {dce=3; dta=Plus; doa=Plus; vaf=3}
    # let etat4 = transition etat3 (Chiffre 2) ;;
    val etat4 : etat = {dce=3; dta=Chiffre 2; doa=Plus; vaf=2}
    # let etat5 = transition etat4 (Chiffre 1) ;;
    val etat5 : etat = {dce=3; dta=Chiffre 1; doa=Plus; vaf=21}
    # let etat6 = transition etat5 Fois ;;
    val etat6 : etat = {dce=24; dta=Fois; doa=Fois; vaf=24}
    # let etat7 = transition etat6 (Chiffre 2) ;;
    val etat7 : etat = {dce=24; dta=Chiffre 2; doa=Fois; vaf=2}
    # let etat8 = transition etat7 Egal ;;
    val etat8 : etat = {dce=48; dta=Egal; doa=Egal; vaf=48}

Cette exécution peut s'écrire de manière plus concise en utilisant une fonction appliquant une suite de transitions correspondant à une liste de touches passée en argument.

    # let transition_liste et lt = List.fold_left transition et lt ;;
    val transition_liste : etat -> touche list -> etat = <fun>
    # let exemple = [ Chiffre 3; Plus; Chiffre 2; Chiffre 1; Fois; Chiffre 2; Egal ]
     in transition_liste etat_initial exemple ;;
    - : etat = {dce=48; dta=Egal; doa=Egal; vaf=48}