Mixing Styles

As we have mentioned, a language offering both functional and
imperative characteristics allows the programmer to choose the more
appropriate style for each part of the implementation of an algorithm.
One can indeed use both aspects in the same function. This is what we
will now illustrate.

Closures and Side Effects

The convention, when a function causes a side effect, is to treat it
as a procedure and to return the value (), of type
unit. Nevertheless, in some cases, it can be useful to cause
the side effect within a function that returns a useful value. We
have already used this mixture of the styles in the function
permute_pivot of quicksort.

The next example is a symbol generator that creates a new symbol each
time that it is called. It simply uses a counter that is incremented
at every call.

This example permits us to illustrate the way that closures are
represented. A closure may be considered as a pair containing the code
(that is, the function part) as one component and the local
envoronment containing the values of the free variables of the
function. Figure 4.1 shows the memory representation of the
closures reset_s and new_s.

Figure 4.1: Memory representation of closures.

These two closures share the same environment, containing the value of
c. When either one modifies the reference c, it
modifies the contents of an area of memory that is shared with the
other closure.

Physical Modifications and Exceptions

Exceptions make it possible to escape from situations in which the
computation cannot proceed. In this case, an exception handler allows
the calculation to continue, knowing that one branch has failed. The
problem with side effects comes from the state of the modifiable data
when the exception was raised. One cannot be sure of this state if
there have been physical modifications in the branch of the
calculation that has failed.

Let us define the increment function (++) analogous to the
operator in C:

# let(++)x=x:=!x+1;x;;val ++ : int ref -> int ref = <fun>

The following example shows a little computation where division by
zero occurs together with

The variable x is not modified during the computation of
the expression in (*1*), while it is modified in the computation of
(*2*). Unless one saves the initial values, the form try ..
with .. must not have a with .. part that depends on
modifiable variables implicated in the expression that raised the
exception.

Modifiable Functional Data Structures

In functional
programming a program (in particular, a function expression) may also
serve as a data object that may be manipulated, and one way to see
this is to write association lists in the form of function
expressions. In fact, one may view association lists of type
('a * 'b) list as partial functions taking a key chosen from
the set 'a and returning a value in the set of associated
values 'b.
Each association list is then a function of type 'a -> 'b.

The empty list is the everywhere undefined function, which one
simulates by raising an exception:

By contrast, writing a function to remove an element from a list is
not trivial, because one no longer has access to the values captured
by the closures. To accomplish the same purpose we mask the former
value by raising the exception
Not_found.

The resulting value for l is a function that points at itself
and therefore loops. This annoying side effect is due to the fact that
the dereferencing !l is within the scope of the closure
functionx->. The value of !l is not evaluated
during compilation, but at run-time. At that time, l points
to the value that has already been modified by add_assoc. We
must therefore correct our definition using the closure created by our
original definition of add_assoc:

Lazy Modifiable Data Structures

Combining imperative characteristics with a functional language
produces good tools for implementing computer languages. In this
subsection, we will illustrate this idea by implementing data
structures with deferred evaluation. A data structure of this kind is
not completely evaluated. Its evaluation progresses according to the
use made of it.

Deferred evaluation, which is often used in purely functional
languages, is simulated using function values, possibly modifiable.
There are at least two purposes for manipulating incompletely
evaluated data structures: first, so as to calculate only what is
effectively needed in the computation; and second, to be able to work
with potentially infinite data structures.

We define the type vm, whose members contain either an
already calculated value (constructor Imm) or else a value to
be calculated (constructor Deferred):

# type'av=Immof'a|Deferredof(unit->'a);;# type'avm={mutablec:'av};;

A computation is deferred by encapsulating it in a closure. The
evaluation function for deferred values must return the value if it
has already been calculated, and otherwise, if the value is not
already calculated, it must evaluate it and then store the result.

then the three arguments of if_function are evaluated at the
time they are passed to the function. So the function fact
loops, because the recursive call fact(n-1) is always
evaluated, even when n has the value 0.

Module Lazy

The implementation difficulty for frozen values is due to the conflict
between the eager evaluation strategy of Objective CAML and the need to
leave expressions unevaluated. Our attempt to redefine the conditional
illustrated this. More generally, it is impossible to write a
function that freezes a value in producing an object of type
vm:

# letfreezee={c=Deferred(fun()->e)};;val freeze : 'a -> 'a vm = <fun>

When this function is applied to arguments, the Objective CAML evaluation
strategy evaluates the expression e passed as argument before
constructing the closure fun()->e. The next example
shows this:

The expression (print_string"Hello") has not been
evaluated, because no message has been printed. The function
force of module Lazy allows one to force evaluation:

# Lazy.forcex;;Hello- : int = 12

Now the value x has altered:

# x;;- : int Lazy.t = {contents=Lazy.Value 12}

It has become the value of the expression that had been frozen, namely
12.

For another call to the function force, it's enough to return
the value already calculated:

# Lazy.forcex;;- : int = 12

The string "Hello" is no longer prefixed.

``Infinite'' Data Structures

The second reason to defer evaluation is to be able to construct
potentially infinite data structures such as the set of natural
numbers. Because it might take a long time to construct them all, the
idea here is to compute only the first one and to know how to pass to
the next element.

We define a generic data structure 'a enum which will allow
us to enumerate the elements of a set.