Desktop Calculator
To understand how a program is built in
Objective CAML, it is necessary to develop one. The chosen example is a
desktop calculator---that is, the simplest model, which only works on whole
numbers and only carries out the four standard arithmetic operations.
To begin, we define the type key to represent the keys of a
pocket calculator. The latter has fifteen keys, namely: one for each
operation, one for each digit, and the = key.
# type
key
=
Plus
|
Minus
|
Times
|
Div
|
Equals
|
Digit
of
int
;;
We note that the numeric keys are gathered under a single constructor
Digit taking an integer argument. In fact, some values of type
key don't actually represent a key. For example, (Digit
3
2
)
is a possible value of type key, but doesn't represent any of
the calculator's keys.
So we write a function valid which verifies that its argument
corresponds to a calculator key. The type of this function is key
-> bool, that is, it takes a value of type key as argument and
returns a value of type bool.
The first step is to define a function which verifies that an integer is
included between 0 and 9. We declare this function under the name
is_digit:
# let
is_digit
=
function
x
->
(x>=
0
)
&&
(x<=
9
)
;;
val is_digit : int -> bool = <fun>
We then define the function valid by pattern-matching over its
argument of type key:
#
let
valid
ky
=
match
ky
with
Digit
n
->
is_digit
n
|
_
->
true
;;
val valid : key -> bool = <fun>
The first pattern is applied when the argument of valid is a value
made with the Digit constructor; in this case, the argument of
Digit is tested by the function is_digit. The second
pattern is applied to every other kind of value of type key.
Recall that thanks to typing, the value being matched is necessarily of
type key.
Before setting out to code the calculator mechanism, we will specify a
model allowing us to describe from a formal point of view the reaction to
the activation of one of the device's keys. We will consider a pocket
calculator to have four registers in which are stored respectively the last
computation done, the last key activated, the last operator
activated, and the number printed on the screen. The set of these four
registers is called the state of the calculator; it is modified by each
keypress on the keypad. This modification is called a transition and the
theory governing this kind of mechanism is that of automata. A state will
be represented in our program by a record type:
# type
state
=
{
lcd
:
int;
(* last computation done *)
lka
:
key;
(* last key activated *)
loa
:
key;
(* last operator activated *)
vpr
:
int
(* value printed *)
}
;;
Figure 2.6 gives an example of a sequence of transitions.
|
state |
key |
|
(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 for 3+21*2=
.
In what follows
we need the function evaluate which takes two integers and a value
of type key containing an operator and which returns the result of
the operation corresponding to the key, applied to the integers. This
function is defined by pattern-matching over its last argument, of type
key:
# let
evaluate
x
y
ky
=
match
ky
with
Plus
->
x
+
y
|
Minus
->
x
-
y
|
Times
->
x
*
y
|
Div
->
x
/
y
|
Equals
->
y
|
Digit
_
->
failwith
"evaluate : no op"
;;
val evaluate : int -> int -> key -> int = <fun>
Now we give the definition of the transition function by enumerating all
possible cases. We assume that the current state is the quadruplet
(a,b,Å,d):
-
a key with digit x is pressed, then there are two cases to
consider:
-
the last key pressed was also a digit. So it is a number which
the user of the pocket calculator is in the midst of entering;
consequently the digit x must be affixed to the printed value, i.e.,
replacing it with d×10+x. The new state is:
(a,(Digit x),Å,d×10+x)
- the last key pressed was not a digit. So it is the start of a new
number which is being entered. The new state is:
(a,(Digit x),Å,x)
- a key with operator Ä has been pressed, the second operand
of the operation has thus been completely entered and the calculator has
to deal with carrying out this operation. It is to this end that the last
operation (here Å) is stored. The new state is:
(Å d,Ä,Ä,a Å d)
To write the function transition, it suffices to translate
the preceding definition word for word into Objective CAML: the definition
by cases becomes a definition by pattern-matching over the key passed as an
argument. The case of a key, which itself is made up of two cases, is handled
by the local function
digit_transition by pattern-matching over
the last key activated.
# let
transition
st
ky
=
let
digit_transition
n
=
function
Digit
_
->
{
st
with
lka=
ky;
vpr=
st.
vpr*
1
0
+
n
}
|
_
->
{
st
with
lka=
ky;
vpr=
n
}
in
match
ky
with
Digit
p
->
digit_transition
p
st.
lka
|
_
->
let
res
=
evaluate
st.
lcd
st.
vpr
st.
loa
in
{
lcd=
res;
lka=
ky;
loa=
ky;
vpr=
res
}
;;
val transition : state -> key -> state = <fun>
This function takes a state and a key and computes the new
state.
We can now test this program on the previous example:
# let
initial_state
=
{
lcd=
0
;
lka=
Equals;
loa=
Equals;
vpr=
0
}
;;
val initial_state : state = {lcd=0; lka=Equals; loa=Equals; vpr=0}
# let
state2
=
transition
initial_state
(Digit
3
)
;;
val state2 : state = {lcd=0; lka=Digit 3; loa=Equals; vpr=3}
# let
state3
=
transition
state2
Plus
;;
val state3 : state = {lcd=3; lka=Plus; loa=Plus; vpr=3}
# let
state4
=
transition
state3
(Digit
2
)
;;
val state4 : state = {lcd=3; lka=Digit 2; loa=Plus; vpr=2}
# let
state5
=
transition
state4
(Digit
1
)
;;
val state5 : state = {lcd=3; lka=Digit 1; loa=Plus; vpr=21}
# let
state6
=
transition
state5
Times
;;
val state6 : state = {lcd=24; lka=Times; loa=Times; vpr=24}
# let
state7
=
transition
state6
(Digit
2
)
;;
val state7 : state = {lcd=24; lka=Digit 2; loa=Times; vpr=2}
# let
state8
=
transition
state7
Equals
;;
val state8 : state = {lcd=48; lka=Equals; loa=Equals; vpr=48}
This run can be written in a more concise way using a function applying a
sequence of transitions corresponding to a list of keys passed as an
argument.
# let
transition_list
st
ls
=
List.fold_left
transition
st
ls
;;
val transition_list : state -> key list -> state = <fun>
# let
example
=
[
Digit
3
;
Plus;
Digit
2
;
Digit
1
;
Times;
Digit
2
;
Equals
]
in
transition_list
initial_state
example
;;
- : state = {lcd=48; lka=Equals; loa=Equals; vpr=48}