## 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*10+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.