Redux logo I happened across this little javascript library called Redux. The link I followed touted it as a silver bullet "backend" library along with React. Now, I despise all these modern javascript fad libraries. So, like a hauty teenager intent on getting outraged by reading facebook posts, I decided to have a closer look...

The Redux website liberally mentions "facebook" <hate> as an inspiration, and CPQS <fad>. The React <more fad, more hate> library is it's biggest consumer. Ooo, I hated it all already. I was getting really angry and worked up, just as intended!

But then I start to notice words like, "immutability", "pure functions", and "single source of truth" and my interest begins to perk up. I read "time-traveling state machine" and I'm intrguied. I then see the teeny tiny 9 method public API and I'm hooked!

I immediately skim read the entire 7 page introdution. I get it, I understand it, but most importantly I WANT IT!

So here's Redux in a nutshell as I see it:

Redux is a methodology for representing your domain model with immutable data. This state can then only be updated by applying small incremental actions. These actions themselves are also immutable and idempotent (pure functions) - which makes your entire system completely deterministic to point where you can even roll your domain back in time!

Yes, Redux is just Event Sourcing at heart, but it's also more than that. It's not just an term or an idea, it's also a framework and methodology for implementing it.

Anyway, I was later soaking in a hot bath and found myself pondering over the design implications of implementing a Redux application and realised I'd already built it! "Eureka!"

The StackHub web application, with its strict adherence to update actions on the domain objects backed by a caching mechanism... is Redux! I just hadn't generalised the code or abstracted out a library. It may be hard coded in the source, but the design ideas were all there!

I was excited again. So much so I felt the need to rewrite Redux in Fantom. I figured it should be easy as Fantom is type safe and has immutability baked in to its core - it lends itself to Redux far more than javascript ever will.

I quickly bashed out a fully functioning and complete bare bones implementation of Redux... in about 25 lines of Fantom code - which runs a faithful representation of the basic Redux Todo example. And here it is:

select all
// ---- Redux ----

class Store {
    private Obj?    rootState
    const   Reducer rootReducer

    StoreListener[] listeners   := StoreListener[,]

    new make(Obj? rootState, Reducer rootReducer) {
        this.rootState   = rootState
        this.rootReducer = rootReducer
    }

    Obj? getState() {
        rootState
    }

    Void dispatch(Action action) {
        rootState = rootReducer.reduce(rootState, action).toImmutable
        listeners.each { it.actionDispatched(this, action) }
    }

    |->| subscribe(StoreListener listener) {
        listeners.add(listener)
        return |->| { listeners.remove(listener) }
    }
}

const mixin StoreListener {
    abstract Void actionDispatched(Store store, Action action)
}

const mixin Action { }

const mixin Reducer {
    abstract Obj? reduce(Obj? state, Action action)
}

Next I'll walk you through the Todo example. It's great for understanding how a Redux application works and how it's all wired together.

The idea is to create a domain model that lets you create a Todo list, mark them complete, and update some view data. But all using immutable state.

I believe the following type safe Fantom implementation of the Todo application is a lot easier to follow and understand than the corresponding javascript one.

So here goes:

1. Define your domain model

You need to first define what data your application will hold. Note how all the classes are const and immutable (thank you Fantom!). This is the key to a deterministic Redux application.

select all
// ---- State ----

const class AppRootState {
    const Str       filter  := "off"
    const Todo[]    todos   := Todo[,]

    new make(|This|? f := null) { f?.call(this) }

    override Str toStr() { "${filter} - ${todos}" }
}

const class Todo {
    const Str       text
    const Bool      isComplete

    new make(|This| f) { f(this) }

    override Str toStr() { (isComplete ? "COMPLETE" : "TODO") + ": ${text}" }
}

2. Define your actions

Actions are write operations that will be performed on your domain. Javascript Redux uses strings to identify unique actions. Here in Fantom land we can use type safe classes instead.

The Action mixin is just used as a marker interface.

select all
// ---- Actions ----

const class AddTodoAction : Action {
    const Str text

    new make(Str text) {
        this.text = text
    }
}

const class ToggleTodoAction : Action {
    const Int todoId

    new make(Int todoId) {
        this.todoId = todoId
    }
}

const class SetVisibilityFilterAction : Action {
    const Str filter

    new make(Str filter) {
        this.filter = filter
    }
}

3. Write your reducers

This is the actual implementation of how you update the immutable domain model.

You tend to write a Reducer for each part of the domain model that needs updating. So we have three, one for the root object, one for the view state, and one for updating the Todo entity.

select all
// ---- Reducers ----

const class AppRootReducer : Reducer {
    override Obj? reduce(Obj? state, Action action) {
        appState := (AppRootState) state
        return AppRootState {
            it.filter = VisibilityReducer().reduce(appState.filter, action)
            it.todos  = TodoReducer().reduce(appState.todos, action)
        }
    }
}

const class VisibilityReducer : Reducer {
    override Obj? reduce(Obj? state, Action action) {
        filter := (Str) state
        if (action is SetVisibilityFilterAction)
            filter = ((SetVisibilityFilterAction) action).filter
        return filter
    }
}

const class TodoReducer : Reducer {
    override Obj? reduce(Obj? state, Action action) {
        todos := (Todo[]) state

        if (action is AddTodoAction) {
            addTodoAction := (AddTodoAction) action
            newTodo := Todo {
                it.text         = addTodoAction.text
                it.isComplete   = false
            }
            todos = todos.rw
            todos.add(newTodo)
        }

        if (action is ToggleTodoAction) {
            toggleTodoAction := (ToggleTodoAction) action
            oldTodo := todos[toggleTodoAction.todoId]
            newTodo := Todo {
                it.text         = oldTodo.text
                it.isComplete   = !oldTodo.isComplete
            }
            todos = todos.rw
            todos[toggleTodoAction.todoId] = newTodo
        }

        return todos
    }
}

Yes, the word Reducer is a terrible, non-descriptive word. The Redux documentation contains paragraphs of text that attempt to justify the use of the word "reduce", which just tells me even they know it's stoopid name!

4. (Optional) Write some listeners

Listeners receive callback events when an action is dispatched / invoked. The example uses them to print out the state of the domain after each action.

// ---- Listeners ----

const class EchoListner : StoreListener {
    override Void actionDispatched(Store store, Action action) {
        echo("${action.typeof.name} -> ${store.getState}")
    }
}

And here is the main example that invokes all of the above. You can see how, line-for-line, it is identical the javascript version.

select all
const class TodoExample {
    static Void main(Str[] args) {
        store := Store(AppRootState(), AppRootReducer())

        echo(store.getState)

        unsubscribe := store.subscribe(EchoListner())

        store.dispatch(AddTodoAction("Learn about actions"))
        store.dispatch(AddTodoAction("Learn about reducers"))
        store.dispatch(AddTodoAction("Learn about store"))
        store.dispatch(ToggleTodoAction(0))
        store.dispatch(ToggleTodoAction(1))
        store.dispatch(SetVisibilityFilterAction("showCompleted"))

        unsubscribe()

        echo(store.getState)
    }
}

Which when run, prints out:

off - [,]
AddTodoAction -> off - [TODO: Learn about actions]
AddTodoAction -> off - [TODO: Learn about actions, TODO: Learn about reducers]
AddTodoAction -> off - [TODO: Learn about actions, TODO: Learn about reducers, TODO: Learn about store]
ToggleTodoAction -> off - [COMPLETE: Learn about actions, TODO: Learn about reducers, TODO: Learn about store]
ToggleTodoAction -> off - [COMPLETE: Learn about actions, COMPLETE: Learn about reducers, TODO: Learn about store]
SetVisibilityFilterAction -> showCompleted - [COMPLETE: Learn about actions, COMPLETE: Learn about reducers, TODO: Learn about store]
showCompleted - [COMPLETE: Learn about actions, COMPLETE: Learn about reducers, TODO: Learn about store]

Admittedly, as the above 100 line example shows, most of the work is done by your application. You have to write the meat of Redux yourself. So the real challenge is yet to come; to see how much boilerplate code can be cut out.

And I already have ideas for that...

Stay tuned!


Discuss