Calculatrice à mémoire
On reprend maintenant l'exemple de la calculatrice décrite dans le
chapitre précédent mais en la dotant cette fois d'une interface
utilisateur rendant notre programme propre à être utilisé comme
une calculette de bureau. Cette boucle permet d'entrer les
opérations directement et de voir s'afficher les résultats sans
avoir à appliquer explicitement la fonction de transition pour chaque
pression d'une touche.
Nous ajoutons quatre nouvelles touches : C qui remet à 0
l'affichage, M qui met en mémoire un résultat, m qui
restitue cette mémoire et OFF qui << éteint >> la
calculatrice. Ceci correspond au type suivant :
# type touche = Plus | Moins | Fois | Par | Egal | Chiffre of int
| MemoireIn | MemoireOut | Clear | Off ;;
Il faut alors définir une fonction de traduction des caractères
frappés au clavier en des valeurs de type touche. L'exception
Touche_non_valide permet de traiter le cas des caractères
ne représentant pas une touche de la calculette. La fonction
code du module Char traduit un caractère en son
code ASCII.
# exception Touche_non_valide ;;
exception Touche_non_valide
# let traduction c = match c with
'+' -> Plus
| '-' -> Moins
| '*' -> Fois
| '/' -> Par
| '=' -> Egal
| 'C' | 'c' -> Clear
| 'M' -> MemoireIn
| 'm' -> MemoireOut
| 'o' | 'O' -> Off
| '0'..'9' as c -> Chiffre ((Char.code c) - (Char.code '0'))
| _ -> raise Touche_non_valide ;;
val traduction : char -> touche = <fun>
Dans un style impératif, la fonction de transition ne calculera plus
un nouvel état, mais modifiera physiquement l'état de la
calculette. Il faut donc redéfinir le type etat de façon
à ce que ses champs soient modifiables. Enfin, on définit
l'exception Touche_off pour traiter l'activation de la touche
OFF.
# type etat = {
mutable dce : int; (* dernier calcul effectué *)
mutable dta : bool; (* vrai si touche = chiffre *)
mutable doa : touche; (* dernier opérateur actionné *)
mutable vaf : int; (* valeur affichée *)
mutable mem : int (* mémoire de la calculette *) };;
# exception Touche_off ;;
exception Touche_off
# let transition et tou = match tou with
Clear -> et.vaf <- 0
| Chiffre n -> et.vaf <- ( if et.dta then et.vaf*10+n else n );
et.dta <- true
| MemoireIn -> et.dta <- false ;
et.mem <- et.vaf
| MemoireOut -> et.dta <- false ;
et.vaf <- et.mem
| Off -> raise Touche_off
| _ -> let dce = match et.doa with
Plus -> et.dce + et.vaf
| Moins -> et.dce - et.vaf
| Fois -> et.dce * et.vaf
| Par -> et.dce / et.vaf
| Egal -> et.vaf
| _ -> failwith "transition: filtre impossible"
in
et.dce <- dce ;
et.dta <- false ;
et.doa <- tou ;
et.vaf <- et.dce;;
val transition : etat -> touche -> unit = <fun>
On définit la fonction go qui lance la calculette.
Sa valeur de retour est () puisque ne nous importe que l'effet
produit par l'exécution sur l'environnement (entrée/sortie,
modification de l'état). Son argument est également la constante
() puisque la calculette est autonome (elle définit elle-même
son état initial) et interactive (les données du calcul
sont rentrées au clavier au fur et à mesure des besoins). Les
transitions s'effectuent dans une boucle infinie (while
true do) dont on pourra sortir par l'exception
Touche_off.
# let go () =
let etat = { dce=0; dta=false; doa=Egal; vaf=0; mem=0 }
in try
while true do
try
let entree = traduction (input_char stdin)
in transition etat entree ;
print_newline () ;
print_string "affichage : " ;
print_int etat.vaf ;
print_newline ()
with
Touche_non_valide -> () (* pas d'effet *)
done
with
Touche_off -> () ;;
val go : unit -> unit = <fun>
Notons que l'état initial doit être soit passé en paramètre, soit
déclaré localement à l'intérieur de la fonction go pour
qu'il soit initialisé à chaque application de cette fonction. Si
nous avions utilisé une valeur etat_initial comme dans le
programme fonctionnel, la calculatrice repartirait dans le même
état que celui qu'elle avait lorsqu'elle a été coupée. Il
aurait été alors difficile d'utiliser deux calculatrices dans le même
programme.