val a = ref 5; val b = ref 5;
We know that the expression a = b;
evaluates to false.
Because ref v
is not equal to ref v
.
They are different locations in memory, each of which happen to contain 5.
Now if we do
a := !a + 1
We have increased the value at the location pointed to by a, but not by b. To do sharing, we need to have said
val b = a;
After this, a=b
is true, and changing a changes b as well.
Also note that in :=
the left hand side must be a ref.
The use of references introduces into our language a new idea of time and sharing. Before, we could consider a binding as assigning a value to a name and expect this value to be constant in time. Each time we created a new binding, we created a new copy of the value, having not possible to share the same structure in memory by two different bindings.
Now if we bind references to values, we can keep a same binding to a reference, but change the pointed value later in time. This introduces to the language a higher complexity that we will need to handle carefully.
ref, !, :=
.
datatype typ = Int_t | Real_t ... | Ref_t of typ (* reference type *) datatype binop = Plus | Times ... | Assign (* := *) datatype unop = Neg (* ~ *) | Not (* not *) | Ref (* ref *) | Deref (* ! *) datatype value = Int_v of int | Real_v of real ... | Ptr_v of int (* address in memory *) | Tag_v of objectTypeThe
:=
is a binary operator and needs to have on the left hand side a Pointer, so we extend evaluateBinop
as follows:
evaluateBinop (bop: binop, (v1,t1): value*typ, (v2,t2): value*typ) case (bop, v1, v2) of ... | (Assign, Ptr_v i, _) => (replaceObjectAt (i, v2); (Tuple_v [], Tuple_t [])) | (Assign, _, _) => err "type error (:=)"
ref
is a unary operator, that takes a value and creates a pointer to this value. !
is an unary operator that "dereferences" the value stored at the location pointed by a specific reference.
For this two operators, we extend the definition of evaluateUnop
as follows:
evaluateUnop (uop: unop, (v, t): value * typ): value * typ = case (uop, v) of ... | (Ref, _) => (Ptr_v (storeObject v), Ref_t t) | (Deref, Ptr_v i) => (case t of Ref_t t' => (loadObject i, t') | _ => err "type error (!)") | (Deref, _) => err "type error (!)"
To do a functional version of this we basically just need a heap structure, which maps addresses to values. This doesn't actually require side effects; we can simply pass it around and update it when needed. The advantage of the evaluator is that it showcases a model that is closer to Intel's.
CS312 � 2002 Cornell University Computer Science |