Comp210 Lecture # 37    Spring 2003

Mutable Lists: LRStruct

As promised, today we will talk about creating a mutable list.

A mutable list, which we'll call "LRStruct" for "Linear Recursive Structure", still follows the same definition as a non-mutable list:

A LRStruct is either
-- empty, or
-- nonEmpty, where it has a first which can by any type and a rest, which is an LRStruct.

A mutable list is still a Composition.

Let's think about what a LRStruct must do:

  1. There must be a get/set on first and rest: "getFirst", "setFirst" , "getRest", "setRest".
  2. "cons" must be replaced with an "insertFirst".
  3. There must be a corresponding "removeFirst".
  4. There must be an "execute" for visitors.

That means that there are 7 intrinsic behaviors for a mutable list -- these behaviors fundamentally define what a mutable list is.

Let's consider this scenario:

  1. We start with an empty LRStruct. Since it is empty, it should act empty, look empty, why, it should even smell empty.

  2. Next, we do an "insertFirst" on this empty LRStruct, inserting some data element. Our LRStruct now is a list with 1 data element--a non-empty list. Thus it should act non-empty, look non-empty, and yes, smell non-empty.

Now that seems all well and dandy, but what really happened here? We started with a bona fide empty list and we ended up with a bona fide non-empty list. But LRStruct is a mutable list, so the empty list we started with and the non-empty list we ended with are the same list! How can a list be both empty and non-empty?

 

 

 

LRStruct exhibits dynamic reclassification ==> LRStruct has 2 states: empty and non-empty.

Mutable lists can do some funky things. Let's look at another scenario:

Suppose we have a function LRSFactory that creates empty LRStructs.

(define l1 (LRSFactory)) ;; l1 is empty

((LRStruct-insertFirst l1) 42) ;; l1 = {42}

(define l1-rest ((LRStruct-getRest l1))) ;; l1-rest is the rest of l1 = {}

((LRStruct-insertFirst l1-rest) 23) ;; l1-rest = {23}

What is l1?

 

insertFirst and cons are not the same thing!

 

Implementation of LRStruct

Since LRStruct has two states, we use the State design pattern to model it:

Note: the "-" in front of a field means that it is "private", that is, it is hidden from view to anyone except the structure (class) that contains it. Also, a blank spot for the scoping (i.e. neither "+" nor "-") means that the field is "package" visible, which means that it is only to be used by those other structures (classes) that are part of the support package for that class, in this case for LRStruct, that would be LRSState, NEState and EmptyState. Scheme syntax is unable to express a package scoping.

Implementation notes:

  1. A per the State design pattern, all that LRStruct actually does is to delegate all function calls to its state.

  2. The state pattern eliminates all conditionals with regards to determining the state of the list.

  3. state, first and rest are encapsulated and hidden using closures.

  4. LRSState, getState and setState are to be used only by LRStruct, NEState and EmptyState, as they are used to manipulate the state of the LRStruct, which is hidden from outside view.

  5. getFirst, setFirst, removeFirst, getRest, and setRest should all cause error conditions when called on an empty LRStruct.

  6. IAlgo is the visitor to a LRStruct and it has the usual empty and non-empty cases.

  7. The state pattern enables the execute function to truly be an intrinsic function of the list. The usual cond statement to determine the list type is completely eliminated. Visitors are now run using the regular syntax for any intrinsic function of a LRStruct:
    ((LRStruct-execute a-LRStruct) a-IAlgo a-param)

    Each state simply and directly calls its respective case in the visitor (note: the host LRStruct is passed to the state during the delegation):

    Empty state: ((IAlgo-emptyCase visitor) host param)
    Non-empty state: ((IAlgo-neCase visitor) host param)

    The invariant (cond [(empty? a-list)...][(cons? a-list)....)]) template is completely gone! It was never the important issue, merely an artifact. All that remains are the truly important base and inductive cases.

  8. To generate the needed closures, a factory, LRSFactory, is also defined and returns an empty LRStruct when run.

To see how LRStruct is implemented in Scheme and to see test code for it and IAlgo, please download the code here: lrstruct.scm

In viewing the test code, do note how the existence of mutability changes the algorithms.

 

Things to note:

 

 

 

 

©2003 Stephen Wong