------------------------

Harvey Mudd College
Computer Science 131
Programming Languages
Spring Semester 1999

Lecture 06 (2/8/99)

------------------------

------------------------

Over the next few weeks we will do almost all our programming in an almost purely functional style. We'll work almost exclusively without a reall "assignment" or variable update. While most of you are probably still skeptical, I think at least a few of you are already beginning to see that in a rich setting like ML, recursion is powerful tool that nearly does away with the need for assignment.

But, while you can write just about anything in this style, the creators of SML realized that there were some times when programming in the traditional imperative way, with updates, is a lot simpler. So, now I want to talk about the question of how to get variable names whose values we can change when we really really really feel we need them.

The way you do this in SML is by the ref type, which really amounts to an explicit pointer. We've already had pointers in our code implicitely. Whenever you use a constructor it really builds a cell with pointers to the component parts of the structures. But using those pointers was entirely hidden, and we could imagine, for example, that the left sub-tree cell of a tree node really did contain the whole left sub-tree rather than a pointer to it. In contrast, refs are basically explicit pointers.

To construct a cell whose conents you want to be able to change and give it a name, you use the ref constructor, which takes as its argument the initial value in the cell. For example:

val x = ref 3;
Now, to update the value of the cell, you use the update operator, which basically dereferences the pointer and changes the value in the cell:
x := 4;
Notice that an update operation has the return value unit. This is true of all side-effects in SML and is done to discourage you from using it as part of a computation and thus confuse it with the functional fragment of the language. To explicitly dereference the pointer and get the value at the end of it, you use the bang (!) operator. Compare the results of:
x;
!x;

Now, to see how this affects things, recall the example from a few lectures back and compare it to the results that occur when refs are used:

val j = 5;
val j1 = ref 5;

fun add_j k = j + k; fun add_j1 k = !j1 + k;

add_j 3; add_j1 3;

val j = 7; j1 := 7;

add_j 3; add_j1 3;

There is an important difference between these pointers and the kind used in C. Since a pointer is only created by the ref constructor, and since there is no explicit pointer disposal function, there is no such thing as an unitialized or dangling pointer! This is part of what it means for ML to be a safe language.

------------------------

A common use of refs is to provide the functionality of static variables in C. That is, to store some local mutable value between calls to a function. For example, we could implement a function that counts how many times it is called by:

local 
   val count = ref 0
in 
   fun foo () = (
                 count := !count + 1 ;
                 !count
                )
end;

Note, the series of expressions separated by semicolons is a sequence. The semicolon in this context is the infix sequencing operator (though it's not defined as an operator, it is special syntax). It evaluates its arguments in order and returns the value of its second argument. This is typically used when side-effects are desired, as with refs or printing.

We can extend this program to allow the starting value to come in as a parameter by building the function on the fly and returning it:

fun new_counter start =
   let
      val count = ref start
   in 
      fn () => (
                count := !count + 1 ;
                !count
               )
   end;

This will allow us to create many counters with separate storage cells.

Similarly, we could create a linear congruential pseudo-random number generator as follows:

local 
   val multiplier = 25173
   val increment = 13849
   val modulus  = 32768
in
   fun new_random seed = 
      let
         val current = ref seed
      in 
         fn range => (
                      current := ((!current * multiplier) + 
                                  increment) mod modulus ;
                      !current mod range
                     )
      end
end;

------------------------

We can take this idea a step further and even get a notion of object with mutable state. The following code will implement a counter object that can be incremented and decremented:

exception illegal_action;
 
fun mk_counter () = 
      let
         val counter = ref 0
      in
         fn "Val" => !counter
          | "Inc" => (
                    counter := !counter + 1; 
                    !counter
                   )
          | "Dec" => (
                    counter := !counter - 1; 
                    !counter
                   )
          | "Reset" => (
                    counter := 0; 
                    !counter
                   )
          | _ => raise illegal_action
      end;

------------------------

One potential problem with refs is that if you are not careful, they open a potential hole in the ML type system.

Consider the declaration:

val x = ref (fn x => x);
What is the type of x?

Then what do we make of the following expression:

(x := (fn x => x + 1);
!x true)
Each part is correctly typed, yet they cannot be combined into a single typable expression.

For this reason, SML restricts refs to being built essentially on monomorphic values. That is, in general you must know their precise type at the time you create them. The type must be known before the ref is created.

The restriction extends up to structures and functions built from refs. So, the function:

fun f x = ref [x];
is legal, but cannot be applied to nil.

This restriction gives rise to the general ``value type restriction'' that we have discussed earlier, and which has plagued us occasionally. For a full description see


http://cm.bell-labs.com/cm/cs/what/smlnj/doc/Conversion/types.html

------------------------
This page copyright ©1999 by Joshua S. Hodas. It was built on a Macintosh. Last rebuilt on Tuesday, February 9, 1999 at 2:09:10 PM.
http://cs.hmc.edu/~hodas/courses/cs131/lectures/lecture06.html