Skip to content

Simple State Machine Examples

This document provides simple, easy-to-understand examples of state machines using mkunion. These examples are designed to help you grasp the core concepts before moving on to more complex scenarios.

Traffic Light Example

A traffic light is a classic example of a state machine with three states: Red, Yellow, and Green.

Model Definition

example/traffic/model.go
package traffic

//go:tag mkunion:"TrafficState,no-type-registry"
type (
    RedLight    struct{}
    YellowLight struct{}
    GreenLight  struct{}
)

//go:tag mkunion:"TrafficCommand,no-type-registry"
type (
    NextCMD struct{} // Move to next state in sequence
)

// Simple traffic light with no dependencies
type Dependencies struct{}

Transition Function

example/traffic/traffic_light.go
// Transition defines the traffic light state transitions
func Transition(ctx context.Context, deps Dependencies, cmd TrafficCommand, state TrafficState) (TrafficState, error) {
    return MatchTrafficCommandR2(cmd,
        func(c *NextCMD) (TrafficState, error) {
            return MatchTrafficStateR2(state,
                func(s *RedLight) (TrafficState, error) {
                    return &GreenLight{}, nil
                },
                func(s *YellowLight) (TrafficState, error) {
                    return &RedLight{}, nil
                },
                func(s *GreenLight) (TrafficState, error) {
                    return &YellowLight{}, nil
                },
            )
        },
    )
}

Testing

example/traffic/traffic_light_test.go
func TestTrafficLightTransitions(t *testing.T) {
    deps := Dependencies{}
    ctx := context.Background()

    // Test red -> green transition
    state, err := Transition(ctx, deps, &NextCMD{}, &RedLight{})
    assert.NoError(t, err)
    assert.IsType(t, &GreenLight{}, state)

    // Test green -> yellow transition
    state, err = Transition(ctx, deps, &NextCMD{}, &GreenLight{})
    assert.NoError(t, err)
    assert.IsType(t, &YellowLight{}, state)

    // Test yellow -> red transition
    state, err = Transition(ctx, deps, &NextCMD{}, &YellowLight{})
    assert.NoError(t, err)
    assert.IsType(t, &RedLight{}, state)
}

Complete Test Suite

example/traffic/traffic_light_test.go
func TestTrafficLightMachine(t *testing.T) {
    suite := machine.NewTestSuite[Dependencies](Dependencies{}, NewMachine)

    suite.Case(t, "traffic light cycle", func(t *testing.T, c *machine.Case[Dependencies, TrafficCommand, TrafficState]) {
        // Start with a red light (default)
        c.
            GivenCommand(&NextCMD{}).
            ThenState(t, &GreenLight{}).
            ForkCase(t, "continue cycle", func(t *testing.T, c *machine.Case[Dependencies, TrafficCommand, TrafficState]) {
                c.
                    GivenCommand(&NextCMD{}).
                    ThenState(t, &YellowLight{}).
                    ForkCase(t, "complete cycle", func(t *testing.T, c *machine.Case[Dependencies, TrafficCommand, TrafficState]) {
                        c.
                            GivenCommand(&NextCMD{}).
                            ThenState(t, &RedLight{})
                    })
            })
    })

    // Generate state diagrams
    if suite.AssertSelfDocumentStateDiagram(t, "traffic_light_test.go") {
        suite.SelfDocumentStateDiagram(t, "traffic_light_test.go")
    }
}

Example Usage

The traffic light state machine can be used in applications:

example/traffic/traffic_light.go
// Example demonstrates using the traffic light state machine
func Example() {
    // Create a new traffic light starting at red
    m := NewMachine(Dependencies{}, &RedLight{})

    // Cycle through the lights
    ctx := context.Background()
    for i := 0; i < 6; i++ {
        state := m.State()
        fmt.Printf("Current light: %T\n", state)

        err := m.Handle(ctx, &NextCMD{})
        if err != nil {
            fmt.Printf("Error: %v\n", err)
            break
        }
    }
    // Output:
    // Current light: *traffic.RedLight
    // Current light: *traffic.GreenLight
    // Current light: *traffic.YellowLight
    // Current light: *traffic.RedLight
    // Current light: *traffic.GreenLight
    // Current light: *traffic.YellowLight
}

Key Concepts Demonstrated

The traffic light example illustrates fundamental state machine concepts:

  1. States without data: Pure states that represent distinct conditions
  2. Simple transitions: Clear, predictable state changes in response to commands
  3. Exhaustive matching: Generated match functions ensure all states are handled
  4. Dependency injection: Even simple examples follow the pattern for consistency
  5. Testability: Easy to test with mkunion's testing framework

Next Steps