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