Monday, October 24, 2016

Event handlers, collectively, provide the control logic in a re-frame application. Since the mutation of the application state is taken care of by re-frame,
these event handlers can be pure functions that given the current value of
the application state as first argument and the event (with its payload)
as second argument, provide a new value for the application state.

This is important because the fact that event handlers are pure functions brings great advantages:

Local reasoning, which decreases the cognitive load required to understand the code.

Easier testing, because pure functions are much easier to test.

Events replay-ability, you can imagine a re-frame application as a reduce (left fold) that proceeds step by step.
Following this mental model, at any point in time, the value of the application state
would be the result of performing a reduce over the entire collection of events dispatched
in the application up until that time, being the combining function for this reduce
the set of registered event handlers.

However, to build a program that does anything useful, it's inevitable to have some side-effects and/or side-causes. So, there would be cases in which event handlers won't be pure functions.

In this post, we'll focus on side-causes.

"Side-causes are data that a function, when called, needs but aren't in its argument list. They are hidden or implicit inputs."

Let's see an example:

The event handler for the :word-ready-to-check event, that we are registering using reg-event-db, is not pure because it's using the value returned by JavaScript's Date.now function instead of getting it through its arguments. To make matters worse, in this particular case, this side-cause also makes the event handler untestable because JavaScript's Date.now function returns a different value each time it's called.

To be able to test this event handler we'll have to somehow stub the function that produces the side-cause. In ClojureScript, there are many ways to do this (using with-redefs, with-bindings, a test doubles library like CircleCI's bond, injecting and stubbing the dependency, etc.).

In this example, we chose to make the dependency that the handler has on a function that returns the current timestamp explicit and inject it into the event handler which becomes a higher order function.

In the previous code example, notice that the event handler now receives as its first parameter a function that returns the current timestamp, time-fn.

And this would be how before registering that event handler with reg-event-db, we perform a partial application to inject JavaScript's Date.now function into it:

Using the same technique, we can now inject a stub of the time-fn function into the event handler in order to test it:

This is the code of the home-made factory to create stubs for time-fn that we used in the previous test:

We've seen how using a test double makes the event handler testable again, but at the price of introducing more complexity.

The bottom line problem is that the event handler is not a pure function. This makes us lose not only the easiness of testing, as we've seen, but also the rest of advantages cited before: local reasoning and events replay-ability. It would be great to have a way to keep event handlers pure, in the presence of side-effects and/or side-causes.

Since re-frame's 0.8.0 (2016.08.19) release, this problem has been solved by introducing the concept of effects and coeffects. Effects represent what your program does to the world (side-effects) while coeffects track what your program requires from the world (side-causes). Now we can write effectful event handlers that keep being pure functions.

Thanks to coeffects, we can eliminate the side-cause, passing the timestamp input through the argument list of the event handler. The resulting event handler is a pure function:

This event handler receives, as its first parameter, a map of coeffects, cofx, which contains two coeffects, represented as key/value pairs. One of them is the current timestamp which is associated to the :timestamp key, and the other one is the application state associated to the :db key. The second argument is, as in previous examples, the event with its payload.

The map of coeffects, cofx, is the complete set of inputs required by the event handler to perform its computation. Notice how the application state is just another coeffect.

This is the same event handler but using destructuring (which is how I usually write them):

How does this work? How does the coeffects map get passed to the event handler?

When you use reg-event-db to associate an event id with the function that handles it, its event handler, that event handler gets as its first argument, the application state, db.

While event handlers registered via reg-event-fx also get two arguments, the first argument is a map of coeffects, cofx, instead of the application state. The application state is still passed in the cofx map as a coeffect associated to the :db key, it's just another coeffect.
This is how the previous pure event handler gets registered:

Notice the second parameter passed to reg-event-fx. This is an optional parameter which is a vector of interceptors. Interceptors are functions that wrap event handlers implementing middleware by assembling functions, as data, in a collection. They "can look after cross-cutting concerns helping to "factor out commonality, hide complexity and introduce further steps into the 'Derived Data, Flowing' story promoted by re-frame".

In this example, we are passing an interceptor created using re-frame's inject-cofx function which returns an interceptor that will load a key/value pair (coeffect id/coeffect value) into the coeffects map just before the event handler is executed.

Second, we factor out the coeffect handler, and then register it using re-frame's reg-cofx. This function associates a coeffect id with the function that injects the corresponding key/value pair into the coeffects map. This function is known as the coeffect handler. For this example, we have:

Since the event handler is now a pure function, it becomes very easy to test it:

Notice how we don't need to use tests doubles anymore in order to test it. Thanks to the use of coeffects, the event handler is a pure function, and we can just pass any timestamp to it in its arguments.

More importantly, we've also regained the advantages of local reasoning and events replay-ability that comes with having pure event handlers.

In future posts, we'll see how we can do something similar using effects to keep events handlers pure in the presence of side-effects.

Using literals in your tests can have some advantages, such as, readability and traceability.

While this is true when the data are simple, it's less so when the data are nested, complex structures.
In that case, using literals can hinder refactoring and thus become an obstacle to adapting to changes.

The problem with using literals for complex, nested data is that the knowledge about how to build such data is spread all over the tests. There are many tests that know about the representation of the data.

In that scenario, nearly any change in the representation of those data will have a big impact on the tests code
because it will force us to change many tests.

This is an example of a test using literals, (from a ClojureScript application
using re-frame, I'm developing with Francesc),
to prepare the application state (usually called db in re-frame):

As you can see, this test knows to much about the structure of db.

There were many other tests doing something similar at some nesting level of the db.

To make things worse, at that moment, we were still learning a lot about the domain, so the structure of the db was suffering changes with every new thing we learned.

The situation was starting to be painful, since any refactoring provoke many changes in the tests, so we decided to fix it.

What we wanted was a way to place all the knowledge about
the representation of the db in just one place (i.e., remove duplication),
so that, in case we needed to change that representation, the impact of the change would be
absorbed by changing only one place.

A nice way of achieving this goal in object-oriented code, and at the same time making
your tests code more readable, is by using test data builders which use the builder pattern, but how can we do these builders in Clojure?

After creating builder functions for some other data used in the project, our test started to read better and to be robust against changes in the structure of db.

For instance, this is the code of the test I showed at the beginning of the post, but now using builder functions instead of literals:

which not only hides the representation of db but also eliminates details that are not used in particular tests, while still being as easy to read, if not easier, than the version using literals.

We have seen how, by composing builder functions and using them in our tests, we managed to reduce
the surface of the impact that changes in the representation of data might have on our tests.
Builder functions absorb the impact of those changes, and enable faster refactoring, and, by doing so, enable adapting to changes faster.