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*
1
0
+
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.