Previous Contents Next

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 32) 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
(24,*,*,24) 2
(24,2,*,2) =

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): 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*10+n }
| _ -> { st with lka=ky; vpr=n }
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}

Previous Contents Next