“Stateless” – You Keep Using That Word…

One of the requirements of pure functions is that they are stateless. To many developers, this means simply that the data upon which the function acts is immutable. When dealing with objects, we mean that the object of an action has immutable fields, set at instantiation and then never changing throughout the instance’s life cycle.

In actual fact, this is not what ‘stateless’ means. Stateless means that the result of an action – e.e. a method call or a function call – is always the same given the same inputs, no matter how many times it’s invoked.

The classic stateless function is one that calculates square roots. sqrt(4) is always 2. sqrt(6.25) is always 2.5, and so on.

The classic stateful function is a light switch. The result of flicking the switch depends on whether the light is on or off at the time. If it’s off, it’s switched on. If it’s on, it’s switched off.

function Light() {
    this.on = false;

    this.flickSwitch = function (){
        this.on = !this.on;
    }
}

let light = new Light();

light.flickSwitch();
console.log(light);

light.flickSwitch();
console.log(light);

light.flickSwitch();
console.log(light);

light.flickSwitch();
console.log(light);

This code produces the output:

{ on: true }
{ on: false }
{ on: true }
{ on: false }

Most domain concepts in the real world are stateful, like our light switch. That is to say, they have a life cycle during which their behaviour changes depending on what has happened to them previously.

This is why finite state machines form a theoretical foundation for all program behaviour. Or, more simply, all program behaviour can be modeled as a finite state machine – a logical map of an object’s life cycle.

lightswitch

Now, a lot of developers would argue that flickSwitch() is stateful because it acts on an object with a mutable field. They would then reason that making on immutable, and producing a copy of the light with it’s state changed, would make it stateless.

const light = {
    on: false
}

function flickSwitch(light){
    return {...light, on: !light.on};
}

const copy1 = flickSwitch(light)
console.log(copy1);

const copy2 = flickSwitch(copy1);
console.log(copy2);

const copy3 = flickSwitch(copy2);
console.log(copy3);

const copy4 = flickSwitch(copy3);
console.log(copy4);

Technically, this is a pure functional implementation of our light switch. No state changes, and the result of each call to flickSwitch() is entirely determined by its input.

But, is it stateless? I mean, is it really? Technically, yes it is. But conceptually, no it certainly isn’t.

If this code was controlling a real light in the real world, then there’s only one light, it’s state changes, and the result of each invocation of flickSwitch() depends on the light’s history.

This is functional programming’s dirty little secret. In memory, it’s stateless and pure functional. Hooray for FP! But at the system level, it’s stateful.

While making it stateless can certainly help us to reason about the logic when considered in isolation – at the unit, or component or service level – when the identity of the object being acted upon is persistent, we lose those benefits at the system level.

Imagine we have two switches controlling a single light (e.g., one at the top of a flight of stairs and one at the bottom.)

lightswitches

In this situation, where a shared object is accessed in two different places, it’s harder to reason about the state of the light without knowing its history.

If I have to replace the bulb, I’d like to know if the light is on or off. With a single switch, I just need to look to see if it’s in the up (off) or down (on) position. With two switches, I need to understand the history. Was it last switched on, or switched off?

Copying immutable objects, when they have persistent identity – it’s the same light – does not make functions that act on those objects stateless. It makes them pure functional, sure. But we still need to consider their history. And in situations of multiple access (concurrency), it’s no less complicated than reasoning about mutable state, and just as prone to errors.

When I was knocking up my little code example, my first implementation of the FP version was:

const light = {
    on: false
}

function flickSwitch(light){
    return {...light, on: !light.on};
}

const copy1 = flickSwitch(light)
console.log(copy1);

const copy2 = flickSwitch(copy1);
console.log(copy2);

const copy3 = flickSwitch(copy2);
console.log(copy3);

const copy4 = flickSwitch(copy3);
console.log(copy3);

Do you see the error? When I ran it, it produced this output.

{ on: true }
{ on: false }
{ on: true }
{ on: true }

This is a class of bug I’ve seen many times in functional code. The last console.log uses the wrong copy.

The order – in this case, the order of copies – matters. And when the order matters, our logic isn’t stateless. It has history.

The most common manifestation of this class of bug I come across is in FP programs that have databases where object state is stored and shared across multiple client threads or processes.

Another workaround is to push the versioning model of our logical design into the database itself, in the form of event sourcing. This again, though, is far from history-agnostic and therefore far from stateless. Each object’s state – rather than being a single record in a single table that changes over time – is now the aggregate of the history of events that mutated it.

Going back to our finite state machine, each object is represented as the sequence of actions that brought it to its current state (e.g., flickSwitch() -> flickSwitch() -> flickSwitch() would produce a light that’s turned on.)

In reasoning about our logic, despite all the spiffy technological workarounds of FP, event sourcing and so on, if objects conceptually have history then they conceptually have state. And at the system level, we have to get that logic conceptually right.

Yet again, technology – including programming paradigm – is no substitute for thinking.

Author: codemanship

Founder of Codemanship Ltd and code craft coach and trainer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s