A C# example.

This article is one of the examples that I promised in the earlier article The State pattern and the State monad. That article examines the relationship between the State design pattern and the State monad. That article is deliberately abstract, so one or more examples are in order.

In this article, I show you how to start with the example from Design Patterns and refactor it to an immutable solution using pure functions.

The code shown here is available on GitHub.

TCP connection #

The example is a class that handles TCP connections. The book's example is in C++, while I'll show my C# interpretation.

A TCP connection can be in one of several states, so the TcpConnection class keeps an instance of the polymorphic TcpState, which implements the state and transitions between them.

TcpConnection plays the role of the State pattern's Context, and TcpState of the State.

public class TcpConnection
{
    public TcpState State { getinternal set; }
 
    public TcpConnection()
    {
        State = TcpClosed.Instance;
    }
 
    public void ActiveOpen()
    {
        State.ActiveOpen(this);
    }
 
    public void PassiveOpen()
    {
        State.PassiveOpen(this);
    }
 
    // More members that delegate to State follows...

The TcpConnection class' methods delegate to a corresponding method on TcpState, passing itself an argument. This gives the TcpState implementation an opportunity to change the TcpConnection's State property, which has an internal setter.

State #

This is the TcpState class:

public class TcpState
{
    public virtual void Transmit(TcpConnection connection, TcpOctetStream stream)
    {
    }
 
    public virtual void ActiveOpen(TcpConnection connection)
    {
    }
 
    public virtual void PassiveOpen(TcpConnection connection)
    {
    }
 
    public virtual void Close(TcpConnection connection)
    {
    }
 
    public virtual void Synchronize(TcpConnection connection)
    {
    }
 
    public virtual void Acknowledge(TcpConnection connection)
    {
    }
 
    public virtual void Send(TcpConnection connection)
    {
    }
}

I don't consider this entirely idiomatic C# code, but it seems closer to the book's C++ example. (It's been a couple of decades since I wrote C++, so I could be mistaken.) It doesn't matter in practice, but instead of a concrete class with no-op virtual methods, I would usually define an interface. I'll do that in the next example article.

The methods have the same names as the methods on TcpConnection, but the signatures are different. All the TcpState methods take a TcpConnection parameter, whereas the TcpConnection methods take no arguments.

While the TcpState methods don't do anything, various classes can inherit from the class and override some or all of them.

Connection closed #

The book shows implementations of three classes that inherit from TcpState, starting with TcpClosed. Here's my translation to C#:

public class TcpClosed : TcpState
{
    public static TcpState Instance = new TcpClosed();
 
    private TcpClosed()
    {
    }
 
    public override void ActiveOpen(TcpConnection connection)
    {
        // Send SYN, receive SYN, Ack, etc.
 
        connection.State = TcpEstablished.Instance;
    }
 
    public override void PassiveOpen(TcpConnection connection)
    {
        connection.State = TcpListen.Instance;
    }
}

This implementation overrides ActiveOpen and PassiveOpen. In both cases, after performing some work, they change connection.State.

"TCPState subclasses maintain no local state, so they can be shared, and only one instance of each is required. The unique instance of TCPState subclass is obtained by the static Instance operation. [...]

"This make each TCPState subclass a Singleton [...]."

I've maintained that property of each subclass in my C# code, even though it has no impact on the structure of the State pattern.

The other subclasses #

The next subclass, TcpEstablished, is cast in the same mould:

public class TcpEstablished : TcpState
{
    public static TcpState Instance = new TcpEstablished();
 
    private TcpEstablished()
    {
    }
 
    public override void Close(TcpConnection connection)
    {
        // send FIN, receive ACK of FIN
 
        connection.State = TcpListen.Instance;
    }
 
    public override void Transmit(
        TcpConnection connection,
        TcpOctetStream stream)
    {
        connection.ProcessOctet(stream);
    }
}

As is TcpListen:

public class TcpListen : TcpState
{
    public static TcpState Instance = new TcpListen();
 
    private TcpListen()
    {
    }
 
    public override void Send(TcpConnection connection)
    {
        // Send SYN, receive SYN, ACK, etc.
 
        connection.State = TcpEstablished.Instance;
    }
}

I admit that I find these examples a bit anaemic, since there's really no logic going on. None of the overrides change state conditionally, which would be possible and make the examples a little more interesting. If you're interested in an example where this happens, see my article Tennis kata using the State pattern.

Refactor to pure functions #

There's only one obvious source of impurity in the example: The literal State mutation of TcpConnection:

public TcpState State { getinternal set; }

While client code can't set the State property, subclasses can, and they do. After all, it's how the State pattern works.

It's quite a stretch to claim that if we can only get rid of that property setter then all else will be pure. After all, who knows what all those comments actually imply:

// Send SYN, receive SYN, ACK, etc.

To be honest, we must imagine that I/O takes place here. This means that even though it's possible to refactor away from mutating the State property, these implementations are not really going to be pure functions.

I could try to imagine what that SYN and ACK would look like, but it would be unfounded and hypothetical. I'm not going to do that here. Instead, that's the reason I'm going to publish a second article with a more realistic and complex example. When it comes to the present example, I'm going to proceed with the unreasonable assumption that the comments hide no nondeterministic behaviour or side effects.

As outlined in the article that compares the State pattern and the State monad, you can refactor state mutation to a pure function by instead returning the new state. Usually, you'd have to return a tuple, because you'd also need to return the 'original' return value. Here, however, the 'return type' of all methods is void, so this isn't necessary.

void is isomorphic to unit, so strictly speaking you could refactor to a return type like Tuple<Unit, TcpConnection>, but that is isomorphic to TcpConnection. (If you need to understand why that is, try writing two functions: One that converts a Tuple<Unit, TcpConnection> to a TcpConnection, and another that converts a TcpConnection to a Tuple<Unit, TcpConnection>.)

There's no reason to make things more complicated than they have to be, so I'm going to use the simplest representation: TcpConnection. Thus, you can get rid of the State mutation by instead returning a new TcpConnection from all methods:

public class TcpConnection
{
    public TcpState State { get; }
 
    public TcpConnection()
    {
        State = TcpClosed.Instance;
    }
 
    private TcpConnection(TcpState state)
    {
        State = state;
    }
 
    public TcpConnection ActiveOpen()
    {
        return new TcpConnection(State.ActiveOpen(this));
    }
 
    public TcpConnection PassiveOpen()
    {
        return new TcpConnection(State.PassiveOpen(this));
    }
 
    // More members that delegate to State follows...

The State property no longer has a setter; there's only a public getter. In order to 'change' the state, code must return a new TcpConnection object with the new state. To facilitate that, you'll need to add a constructor overload that takes the new state as an input. Here I made it private, but making it more accessible is not prohibited.

This implies, however, that the TcpState methods also return values instead of mutating state. The base class now looks like this:

public class TcpState
{
    public virtual TcpState Transmit(TcpConnection connection, TcpOctetStream stream)
    {
        return this;
    }
 
    public virtual TcpState ActiveOpen(TcpConnection connection)
    {
        return this;
    }
 
    public virtual TcpState PassiveOpen(TcpConnection connection)
    {
        return this;
    }
 
    // And so on...

Again, all the methods previously 'returned' void, so while, according to the State monad, you should strictly speaking return Tuple<Unit, TcpState>, this simplifies to TcpState.

Individual subclasses now do their work and return other TcpState implementations. I'm not going to tire you with all the example subclasses, so here's just TcpEstablished:

public class TcpEstablished : TcpState
{
    public static TcpState Instance = new TcpEstablished();
 
    private TcpEstablished()
    {
    }
 
    public override TcpState Close(TcpConnection connection)
    {
        // send FIN, receive ACK of FIN
 
        return TcpListen.Instance;
    }
 
    public override TcpState Transmit(
        TcpConnection connection,
        TcpOctetStream stream)
    {
        TcpConnection newConnection = connection.ProcessOctet(stream);
        return newConnection.State;
    }
}

The trickiest implementation is Transmit, since ProcessOctet returns a TcpConnection while the Transmit method has to return a TcpState. Fortunately, the Transmit method can achieve that goal by returning newConnection.State. It feels a bit roundabout, but highlights a point I made in the previous article: The TcpConnection and TcpState classes are isomorphic - or, they would be if we made the TcpConnection constructor overload public. Thus, the TcpConnection class is redundant and might be deleted.

Conclusion #

This article shows how to refactor the TCP connection sample code from Design Patterns to pure functions.

If it feels as though something's missing there's a good reason for that. The example, as given, is degenerate because all methods 'return' void, and we don't really know what the actual implementation code (all that Send SYN, receive SYN, ACK, etc.) looks like. This means that we actually don't have to make use of the State monad, because we can get away with endomorphisms. All methods on TcpConnection are really functions that take TcpConnection as input (the instance itself) and return TcpConnection. If you want to see a more realistic example showcasing that perspective, see my article From State tennis to endomorphism.

Even though the example is degenerate, I wanted to show it because otherwise you might wonder how the book's example code fares when exposed to the State monad. To be clear, because of the nature of the example, the State monad never becomes necessary. Thus, we need a second example.

Next: Refactoring a saga from the State pattern to the State monad.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 26 September 2022 05:50:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 26 September 2022 05:50:00 UTC