|
Comp210: Principles of Computing and Programming
Fall 2004 -- Lecture
#37
|
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:
- There must be a get/set on first and rest: "getFirst",
"setFirst" , "getRest",
"setRest".
- "cons" must be replaced with an "insertFirst".
- There must be a corresponding "removeFirst".
- 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:
- We start with an empty LRStruct.
Since it is empty, it should act empty, look empty, why, it should even
smell empty.
- 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:
- A per the State design pattern, all that LRStruct actually does is to
delegate all function calls to its state.
- The state pattern eliminates all conditionals with regards to
determining the state of the list.
- state, first and
rest are encapsulated and hidden using closures.
- 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.
- getFirst, setFirst, removeFirst,
getRest, and setRest
should all cause error conditions when called on an empty
LRStruct.
- IAlgo is the visitor to a LRStruct and it has the usual empty and
non-empty cases.
- 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.
- 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:
-
LRStruct
is 100% invariant code. It contains all the functionality needed
for it to model a mutable list.
- All algorithms on
LRStruct
are written in terms of IAlgos
(variant code). All algorithms can thus be described in terms of a
empty and non-empty cases utilizing the 7 intrinsic behaviors of a mutable
list.
- IAlgos
are components that fit into the
LRStruct
framework.
- Conditionals involving the type of the list are
non-existent.
- All algorithms on
LRStructs are independent of the implementation of
LRStruct
and are thus "decoupled" from the list, and run at the level of
abstraction of the list, not its states.
Last Revised
Tuesday, 24-Aug-2004 13:49:07 CDT
©2004 Stephen Wong and Dung Nguyen