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)
- 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 a different
level of abstraction.
©2002 Stephen Wong