One of the stumbling blocks that gets in the way of people adopting immutable types is a belief that only trivial data structures can be created immutable; that mutability is a prerequisite for doing any real work. It turns out that this isn’t true - in this series of posts, I’ll explore “doing real work” with immutable data structures.

Let’s consider the humble stack - what can we learn from creating an immutable version? We’ll start by declaring an interface:

public interface IImmutableStack<T>
{
    /// <summary>
    /// Test to see whether we have an empty stack
    /// </summary>
    bool Empty { get; }

    /// <summary>
    /// Gets the item on the top of the stack
    /// </summary>
    T Peek { get; }

    /// <summary>
    /// Push a new item on the stack, returning the new stack
    /// </summary>
    /// <param name="value">Value to be pushed on the top of the stack.</param>
    /// <returns>New stack with the new value on top.</returns>
    IImmutableStack<T> Push(T value);

    /// <summary>
    /// Discard the top item from the stack, returning a smaller stack.
    /// </summary>
    /// <returns>A smaller stack lacking the top item we've discarded.</returns>
    IImmutableStack<T> Discard();
}

What should our implementation look like?

Perhaps the key realization is that any non-trivial stack contains two things - the item that’s on top, and a smaller stack beneath. We can use this to create a simple implementation:

// Internal class because we want to expose the interface instead
internal class SimpleImmutableStack<T> : IImmutableStack<T>
{
    // We don't need a copy of the underlying stack, just a reference
    private readonly IImmutableStack<T> _rest;

    // A simple stack is never empty
    public bool Empty => false;

    public T Peek { get; }

    // To discard our top item, just return our underlying stack
    public IImmutableStack<T> Discard() => _rest;

    // To push an item on top, create a new stack
    public IImmutableStack<T> Push(T value)
    {
        return new SimpleImmutableStack<T>(value, this);
    }
        
    public SimpleImmutableStack(
        T value, IImmutableStack<T> rest)
    {
        Peek = value;
        _rest = rest;
    }
}

This is a bit circular because you have to already have a stack in order to create a new stack. The constructor needs a stack as one of the parameters - how do we get started?

We need to define an empty stack too.

// Again, an internal class because we are exposing the interface instead
internal class EmptyImmutableStack<T> : IImmutableStack<T>
{
    // An empty stack is always empty 
    public bool Empty => true;

    // Can never peek an empty stack
    public T Peek => throw new InvalidOperationException();

    // Can't discard the top of an empty stack
    public IImmutableStack<T> Discard()
    {
        throw new InvalidOperationException();
    }

    public IImmutableStack<T> Push(T value)
    {
        return new SimpleImmutableStack<T>(value, this);
    }
}

Lastly, we define a very simple entry point by creating an easy way to access an empty stack.

public static class ImmutableStack<T>
{
    // Initialised once and shared
    public static IImmutableStack<T> Empty
        => new EmptyImmutableStack<T>();
}

Keeping this static class separate from the implementations is why our first class was named SimpleImmutableStack<T>.

This is a very simple immutable class, but one that provides all the required functionality of a stack. Notice how pushing a new item onto the stack doesn’t involve any copying of data; instead, the new stack just keeps a reference to note that the rest of the stack is “over there”.

Updated 23/1/2017: The implementation for EmptyImmutableStack<T>.Peek now uses an expression bodied declaration.

Comments

blog comments powered by Disqus