

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 | |