Calculator With Memory
We now reuse the calculator example described in the preceding
chapter, but this time we give it a user interface, which makes our
program more usable as a desktop calculator. This loop allows entering
operations directly and seeing results displayed without having to
explicitly apply a transition function for each keypress.
We attach four new keys: C, which resets the display to zero,
M, which memorizes a result, m, which recalls this memory and
OFF, which turns off the calculator.
This corresponds to the following type:
# type
key
=
Plus
|
Minus
|
Times
|
Div
|
Equals
|
Digit
of
int
|
Store
|
Recall
|
Clear
|
Off
;;
It is necessary to define a translation function from characters
typed on the keyboard to values of type key. The exception
Invalid_key handles the case of characters that do not
represent any key of the calculator. The function code of module
Char translates a character to its ASCII-code.
# exception
Invalid_key
;;
exception Invalid_key
# let
translation
c
=
match
c
with
'+'
->
Plus
|
'-'
->
Minus
|
'*'
->
Times
|
'/'
->
Div
|
'='
->
Equals
|
'C'
|
'c'
->
Clear
|
'M'
->
Store
|
'm'
->
Recall
|
'o'
|
'O'
->
Off
|
'0'
..
'9'
as
c
->
Digit
((Char.code
c)
-
(Char.code
'0'
))
|
_
->
raise
Invalid_key
;;
val translation : char -> key = <fun>
In imperative style, the translation function does not calculate a new
state, but physically modifies the state of the calculator. Therefore, it is
necessary to redefine the type state such that the fields are
modifiable. Finally, we define the exception Key_off for treating the
activation of the key OFF.
# type
state
=
{
mutable
lcd
:
int;
(* last computation done *)
mutable
lka
:
bool;
(* last key activated *)
mutable
loa
:
key;
(* last operator activated *)
mutable
vpr
:
int;
(* value printed *)
mutable
mem
:
int
(* memory of calculator *)
};;
# exception
Key_off
;;
exception Key_off
# let
transition
s
key
=
match
key
with
Clear
->
s.
vpr
<-
0
|
Digit
n
->
s.
vpr
<-
(
if
s.
lka
then
s.
vpr*
1
0
+
n
else
n
);
s.
lka
<-
true
|
Store
->
s.
lka
<-
false
;
s.
mem
<-
s.
vpr
|
Recall
->
s.
lka
<-
false
;
s.
vpr
<-
s.
mem
|
Off
->
raise
Key_off
|
_
->
let
lcd
=
match
s.
loa
with
Plus
->
s.
lcd
+
s.
vpr
|
Minus
->
s.
lcd
-
s.
vpr
|
Times
->
s.
lcd
*
s.
vpr
|
Div
->
s.
lcd
/
s.
vpr
|
Equals
->
s.
vpr
|
_
->
failwith
"transition: impossible match"
in
s.
lcd
<-
lcd
;
s.
lka
<-
false
;
s.
loa
<-
key
;
s.
vpr
<-
s.
lcd;;
val transition : state -> key -> unit = <fun>
We define the function go, which starts the calculator.
Its return value is (), because we are only concerned about
effects produced by the execution on the environment (start/end,
modification of state). Its argument is also the constant (),
because the calculator is autonomous (it defines its own initial state)
and interactive (the arguments of the computation are entered on the
keyboard as required). The transitions are performed within an infinite
loop (while true do) so we can quit with the
exception Key_off.
# let
go
()
=
let
state
=
{
lcd=
0
;
lka=
false;
loa=
Equals;
vpr=
0
;
mem=
0
}
in
try
while
true
do
try
let
input
=
translation
(input_char
stdin)
in
transition
state
input
;
print_newline
()
;
print_string
"result: "
;
print_int
state.
vpr
;
print_newline
()
with
Invalid_key
->
()
(* no effect *)
done
with
Key_off
->
()
;;
val go : unit -> unit = <fun>
We note that the initial state must be either passed as a parameter or
declared locally within the function go, because it needs to
be initialized at every application of this function. If we had used a
value initial_state as in the functional program, the calculator
would start in the same state as the one it had when it was terminated.
This would make it difficult to use two calculators in the same program.