Précédent Index Suivant

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 32) 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) : 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}







Précédent Index Suivant