In my article The State of Things I introduced the idea of a run-time state machine: one which is assembled at run-time and dynamically delegates actions to each state. The premise is that state machines tend to be alike, therefore it makes sense to factor out the common code. A programmer's wet dream. This is a departure from how the state machine design pattern is commonly implemented; in which each state machine is crafted out of its own set of domain-specific classes.

Since the action method calls are not known at compile-time, run-time state machines apply only to dynamic/multi-dispatch programming languages, such as Groovy. Here's where the aforementioned article left off:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class StateMachine {
    Map context
    Map<String, State> states
    State state
    
    def methodMissing(String name, args) {
        def action = state.actions[name]
        
        if(action instanceof GroovyCallable) {
            action.delegate = context
            state = states[action(*args)] ?: state
            return null
        } else if(action instanceof String) {
            state = states[action] ?: state
        } else {
            throw new RuntimeException("'$name' is an invalid action for state '$state.name'.")
        }        
    }
}

class State {
    String name
    Map actions = [:]
}

def sm = new StateMachine(
    context: [inventory: 0],
    states: [
        SoldOut: [
            fill: { count ->
                delegate.inventory = delegate.inventory + count
                delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
            }
        ],
        NoCurrency: [
            insertCurrency: 'HasCurrency'
        ],
        HasCurrency: [
            purchase: {
                --delegate.inventory
                delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
            },
            eject: 'NoCurrency'
        ]
    ].collectEntries { name, actions -> [name, new State(name: name, actions: actions)] }
)

sm.state = sm.context.inventory > 0 ? sm.states.NoCurrency : sm.states.SoldOut
sm.fill(2)

sm.insertCurrency()
sm.purchase()
assert sm.state.name == 'NoCurrency'

sm.insertCurrency()
sm.purchase()
assert sm.state.name == 'SoldOut'

The run-time state machine, referred to hereafter as simply state machine, is build by calling the constructor with the context, states, and state actions/transitions. As the state machine is used, actions are dynamically delegated to the current state. In this article I explore mashing up this state machine with a builder in order to simplify the construction of state machines. But first, lets take a smaller step and use a factory method.

A factory of states

An issue with the build process is the need to create the State instances. A simple factory method can alleviate this issue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class StateMachine {
    ... 

    static StateMachine build(Map context, Map states) {
        new StateMachine(
            context: context, 
            states: states.collectEntries { 
                name, actions -> [name, new State(name: name, actions: actions)]
            }
        )
    }
}

def sm = StateMachine.build(
    [inventory: 0],
    [
        SoldOut: [
            fill: { count ->
                delegate.inventory = delegate.inventory + count
                delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
            }
        ],
        NoCurrency: [
            insertCurrency: 'HasCurrency'
        ],
        HasCurrency: [
            purchase: {
                --delegate.inventory
                delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
            },
            eject: 'NoCurrency'
        ]
    ]
)

The factory method StateMachine.build(Map, Map) makes it possible to declare the states and actions as nested Maps without knowledge of the State class. It's a simple step forward, but adding a builder would be even better.

Erection of the state

Imagine building a state machine with a syntax like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
StateMachine.build(inventory: 0) { // <-- statesClosure
    SoldOut { // <-- actionsClosure
        fill { count -> // <-- actionClosure
            delegate.inventory = delegate.inventory + count
            delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
        }
    }

    NoCurrency {
        insertCurrency = 'HasCurrency'
    }

    HasCurrency {
        purchase {
            --delegate.inventory
            delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
        }

        eject = 'NoCurrency'
    }
}

StateMachine.build(Map, Closure) accepts a Map for the context and a Closure specifying the states and actions. To construct States, a top-level method call would be intercepted and a State instance initialized. Then, the second-level method calls would be intercepted to create the Map of actions for the State. As a bonus, property assignment can be used instead of a method call for actions which merely transition to another state.

State machine builder

Notice that the actions closures, which are the Closures to be invoked to perform a state action, are not evaluated by the builder. This is an important difference between this builder and Groovy's BuilderSupport.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class StatesBuilder {
    Map states = [:]
    
    def methodMissing(String name, Object args) {
        def state = new State(name: name)
        def closure = args[0]
        def builder = new ActionsBuilder(state)
        
        builder.with(closure)
        states[name] = state
        
        return state
    }
}

@groovy.transform.TupleConstructor
class ActionsBuilder {
    State state
    
    def methodMissing(String name, Object args) {
        state.actions[name] = args[0]
    }
    
    def propertyMissing(String name, value) {
        state.actions[name] = value
    }
}

The StatesBuilder class dynamically handles method calls such as SoldOut(Closure) and NoCurrency(Closure), creating State instances as it goes. The ActionsBuilder class handles the action method calls, like fill(Closure), and also property assignments, such as insertCurrency = 'HasCurrency'. These two builder classes can successfully construct a state machine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def sm = new StateMachine(context: [inventory: 0])
def builder = new StatesBuilder()

builder.with {
    SoldOut {
        fill { count ->
            delegate.inventory = delegate.inventory + count
            delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
        }
    }
    
    NoCurrency {
        insertCurrency = 'HasCurrency'
    }
    
    HasCurrency {
        purchase {
            --delegate.inventory
            delegate.inventory > 0 ? 'NoCurrency' : 'SoldOut'
        }

        eject = 'NoCurrency'
    }
}

sm.states = builder.states

The final step is to integrate the builder classes into the factory method StateMachine.build(Map, Closure).

1
2
3
4
5
6
7
8
9
10
11
12
13
class StateMachine {
    ...
 
    static StateMachine build(Map context, Closure closure) {
        def sm = new StateMachine(context: context)
        def builder = new StatesBuilder()
                
        builder.with(closure)        
        sm.states = builder.states
        
        return sm
    }
}

State inspection

Now, we both know that in programming there's no real final step. Reviewing the code reveals a couple of areas which could be improved:

  1. Initializing the state machine
  2. Transparently accessing the context within an action

Initialization

Currently the state machine is initialized manually after it has been constructed.

1
sm.state = sm.context.inventory > 0 ? sm.states.NoCurrency : sm.states.SoldOut

There are numerous ways to make initialization part of the build process. I'll take a cue from Python and give special privileges to a method named __init__(Closure):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class StatesBuilder {
    ...

    def __init__(Closure closure) {
        initClosure = closure
    }
}

class StateMachine {
    ...
 
    static StateMachine build(Map context, Closure closure) {
        def sm = new StateMachine(context: context)
        def builder = new StatesBuilder()
                
        builder.with(closure)        
        sm.states = builder.states
        
        // NEW CODE! Initializes state machine
        if(builder.initClosure) {
            sm.state = builder.initClosure(sm)
        }
        
        return sm
    }
}

Now the state machine can be initialized during the build by implementing the __init__ Closure:

1
2
3
4
5
6
7
def sm = StateMachine.build(inventory: 0) {
    __init__ { StateMachine sm ->
        sm.context.inventory > 0 ? sm.states.NoCurrency : sm.states.SoldOut
    }
    
    ...    
}

Transparent context

A simple change, setting the delegation strategy to DELEGATE_FIRST, is enough to grant easy access to the context within an action method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class StateMachine {

    ...

    def methodMissing(String name, args) {
        ...
 
        if(action instanceof GroovyCallable) {
            action.delegate = context
            action.resolveStrategy = Closure.DELEGATE_FIRST
        ...
    }

    ...
}

With the above code in place the fill and purchase actions can access the context using property access.

1
2
3
4
5
6
7
8
9
fill { count ->            
    inventory = inventory + count
    inventory > 0 ? 'NoCurrency' : 'SoldOut'
}

purchase {
    --inventory
    inventory > 0 ? 'NoCurrency' : 'SoldOut'
}

Oh, how pretty that is!

The building blocks

To summarize, lets build and use one of these fancy state machines.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def sm = StateMachine.build(inventory: 0) {
    __init__ { StateMachine sm ->
        sm.context.inventory > 0 ? sm.states.NoCurrency : sm.states.SoldOut
    }
    
    SoldOut {
        fill { count ->            
            inventory = inventory + count
            inventory > 0 ? 'NoCurrency' : 'SoldOut'
        }
    }
    
    NoCurrency {
        insertCurrency = 'HasCurrency'
    }
    
    HasCurrency {
        purchase {
            --inventory
            inventory > 0 ? 'NoCurrency' : 'SoldOut'
        }

        eject = 'NoCurrency'
    }
}

sm.fill(2)
sm.insertCurrency()
sm.purchase()
assert sm.state.name == 'NoCurrency'

sm.insertCurrency()
sm.purchase()
assert sm.state.name == 'SoldOut'

try {
    sm.insertCurrency()
    assert false
} catch (RuntimeException e) { 
    assert e.message == "'insertCurrency' is an invalid action for state 'SoldOut'."
}

Now that's freakin' awesome. Putting a state machine together is a matter of declaring each state and its actions. Then, you can just... use it. The state transitions are handled automatically, the context is easily accessible, and the best part: there's no need to code yet another set of state machine classes.

And of course, as good as this is already, I'm certain improvements will come.