Some languages, notably ML and its derivatives, make a typed distinction between immutable and mutable data. This distinction pursues the noble goal of building abstractions that cannot be subverted by their own users. However, the mechanism used to implement this distinction (i.e. reference cells) seems too crude to me for two reasons.
The first and more superficial reason is that reference cells require too much syntactic ceremony. The only things you can do directly with a cell are unpack its current state and pack a new one. The state of a cell is a first-class value, distinct from the cell itself, whether you need this feature or not.
The second and more substantial reason is that reference cells do not provide any means to control how their contents evolve over time, which means that any cell can be set to any new state at any time. In practice, when I use reference cells, I have a life cycle for them in mind, which determines which state transitions make sense. More precisely:
- The state type of a cell is a
datatype, which is never used for any other purpose, i.e., state values exist solely to be stored in reference cells.
- There exists a directed graph whose nodes are the
datatype‘s constructors. A reference cell in state
A xcan be set to state
B yif and only if there is an edge
A -> Bin the aforementioned graph.
Hence, I would like to dispense with reference cells altogether, and extend
datatypes with mutability annotations. For example, a type of suspensions could be implemented as
datatype 'a susp = Eager of 'a | Lazy of unit -> 'a mutates Lazy -> Eager val eager = Eager fun lazy f x = Lazy (fn _ => f x) fun force (Eager x) = x | force (Lazy f) := Eager (f ())
Note that, under my proposal, after turning a
Lazy suspension into an
Eager one, the control flow jumps to the pattern matching arm that handles the