Gardening passes over docs

This commit is contained in:
Mike Thompson 2016-08-14 14:42:32 +10:00
parent 3bcba7b796
commit 108ee61220
2 changed files with 85 additions and 72 deletions

View File

@ -7,7 +7,7 @@ Yes, a surprising claim.
Events "happen" when they are dispatched.
So, this causes an event:
So, this makes an event happen:
```clj
(dispatch [:set-flag true])
```
@ -26,7 +26,7 @@ And that state change is very desirable. Without the state change our
application can't incorporate that button click, or the newly arrived
websocket message. Without mutation, the apps just sits there, stuck.
State change is how the application "moves forward" - how it does its job.
State change is how the application "moves forward" - how it does its job. Useful!
On the other hand, control logic and state mutation tend to be the most
complex and error prone of part of an app.
@ -39,24 +39,24 @@ provided you with a simple programming model.
It said you should call `reg-event-db` to associate an event id,
with a function to do the handling:
```clj
(re-frame.core/reg-event-db
:set-flag
(fn [db [_ new-value]
(re-frame.core/reg-event-db ;; <-- call this to register handlers
:set-flag ;; this is an event id
(fn [db [_ new-value] ;; this function does the handling
(assoc db :flag new-value)))
```
The function you register, actions the event.
The function you register, handles events with a given `id`.
And that handler `fn` was expected to be pure. Given the
value in `app-db` as the first argument, and the event (including params)
as the second argument, it is expected to provide the new value for `app-db`.
And that handler `fn` is expected to be pure. Given the
value in `app-db` as the first argument, and the event (vector)
as the second argument, it is expected to provide a new value for `app-db`.
Data in, a computation and data out. Pure.
### 90% Solution
This paradigm provides a lovely solution 90% of the time, but there are times
when it fails us.
when it isn't enough.
Here's an example from the messy 10%. To get its job done, this handler has to side effect:
```clj
@ -67,11 +67,11 @@ Here's an example from the messy 10%. To get its job done, this handler has to s
(assoc db :send-spam new-val)))
```
That `dispatch` queues up another event to be processed. It changes the world.
That `dispatch` queues up another event to be processed. It changes the world.
Just to be clear, this code works. The handler returns a new version of `db`, so tick,
and that `dispatch` will itself be "handled" asynchronously
very shortly after this handler finishes, double tick.
very shortly after this handler finishes, double tick.
So, you can "get away with it". But it ain't pure.
@ -99,7 +99,7 @@ consequences:
using the right URL? "mocking" should be is mocked. It is a code smell.
3. And event replay-ability is lost.
Regarding the 3rd point, a re-frame application proceeds step by step, like a reduce. From the README:
Regarding the 3rd point above, a re-frame application proceeds step by step, like a reduce. From the README:
> at any one time, the value in app-db is the result of performing a reduce over the entire
> collection of events dispatched in the app up until that time. The combining
@ -112,12 +112,12 @@ replay, inserting extra events into it, etc, which ruins the process.
### The Other Problem
Here's another purity problem:
And there's another purity problem:
```clj
(reg-event-db
:load-localstore
(fn [db _]
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <--
(let [val (js->clj (.getItem js/localStorage "defaults-key"))] ;; <-- Problem
(assoc db :defaults val))))
```
@ -127,13 +127,13 @@ So this handler has no side effect - it doesn't need to change the world - __but
need to source data from somewhere other than its arguments - from somewhere
outside of app-db or the event.
It isn't a pure function which leads to the normal problems.
So, it isn't a pure function, and that leads to the normal problems.
### Effects And Coeffects
So there are [two concepts at play here](http://tomasp.net/blog/2014/why-coeffects-matter/):
- **Effects** - what your event handler does to the world (aka side-effects)
- **Coeffects** - what your event handler requires from the world (aka [side-causes](http://blog.jenkster.com/2015/12/what-is-functional-programming.html))
- **Coeffects** - the data your event handler requires from the world in order to do its computation (aka [side-causes](http://blog.jenkster.com/2015/12/what-is-functional-programming.html))
We'll need a solution for both.
@ -149,7 +149,7 @@ There's just no getting away from living in a mutative world, er,
which sounds ominous. Is that it? Are we doomed to impurity?
Well, luckily a small twist in the tale makes a profound difference. We
will look at side effects first. Instead of creating event handlers
will look at side-effects first. Instead of creating event handlers
which *do side-effects*, we'll instead get them to *cause side-effects*.
### Doing vs Causing
@ -159,28 +159,28 @@ Above, I proudly claimed that this `fn` event handler was pure:
(reg-event-db
:my-event
(fn [db _]
(assoc db :flag true)))
(assoc db :flag true)))
```
Takes a `db` value, computes and returns a `db` value. No coeffects or effects. Yep, that's Pure!
All true, but ... this purity is only possible because re-frame is doing
Yes, all true, but ... this purity is only possible because re-frame is doing
the necessary side-effecting.
Wait on. What "necessary side-effecting"?
Well, application state is stored in `app-db`, right? And it is a ratom. After
Well, application state is stored in `app-db`, right? And it is a ratom. And after
each event handler runs, it must be `reset!` to the newly returned
value. That, right there, is the necessary side effecting.
value. That, right there, is the "necessary side effecting".
We get to live in our ascetic functional world because re-frame is
looking after the "necessary side-effects" on `app-db`. Interesting.
looking after the "necessary side-effects" on `app-db`.
### Et tu, React?
Turns out it's the same pattern with Reagent/React.
We get to write a nice pure component:
We get to write a nice pure component, like:
```clj
(defn say-hi
[name]
@ -191,9 +191,9 @@ after the "necessary side-effects".
### Pattern Structure
Pause and look back at `say-hi`. I'd like you to view it through the following lens: it is a pure
function which **returns a description of the side-effects required**. It says: add a div element
to the DOM.
Pause and look back at `say-hi`. I'd like you to view it through the
following lens: it is a pure function which **returns a description
of the side-effects required**. It says: add a div element to the DOM.
Notice that the description is declarative. We don't tell React how to do it.
@ -203,7 +203,7 @@ This is a big, important concept. While we can't get away from certain side-eff
program using pure functions which **describe side-effects, declaratively, in data** and
let the backing framework look after the "doing" of them. Efficiently. Discreetly.
Let's use this pattern to solve the side-effecting handler problem.
Let's use this pattern to solve the side-effecting event-handler problem.
### The Two Step Plan
@ -235,8 +235,8 @@ Here it is re-written so as to be pure:
```
Notes: <br>
*<1>* we're using `reg-event-fx` instead of `reg-event-db` to register (that's `-db` bs `-fx`) <br>
*<2>* the first parameter is nolonger just `db`. It is a map from which
*<1>* we're using `reg-event-fx` instead of `reg-event-db` to register (that's `-db` vs `-fx`) <br>
*<2>* the first parameter is no longer just `db`. It is a map from which
[we are destructuring db](http://clojure.org/guides/destructuring). Ie.
it is a map which contains a `:db` key. <br>
*<3>* The handler is returning a data structure (map) which describes two side-effects:
@ -287,21 +287,21 @@ So far we've written our new style `-fx handlers like this:
{ ... }))
```
It is now time to name that first parameter:
It is now time to name that first argument:
```clj
(reg-event-fx
:my-event
(fn [coeffects event] ;; <--- it has a name
(fn [coeffects event] ;; <--- thy name be coeefects
{ ... }))
```
When you use the `-fx` form of registration, the first argument of your handler will be a coeffects map.
In that map will be the complete set of "inputs" required by your function. The complete
set of computational resources (data) which it needs to perform its computation. But how?
I'll explain soon enough, I promise, but for the moment, take it as a magical given.
set of computational resources (data) needed to perform its computation. But how?
I'll explain in an upcoming tutorial, I promise, but for the moment, take it as a magical given.
One of the keys in `coeffects` will likely be `:db` and that will be the value in app-db.
One of the keys in `coeffects` will likely be `:db` and that will be the value of `app-db`.
Remember this impure handler from before:
```clj
@ -318,13 +318,15 @@ We'd now rewrite that as a pure handler, like this:
:load-localstore
(fn [coeffect _] ;; coeffect is a map containing inputs
(let [defaults (:defaults-key coeffect)] ;; <-- use it here
(assoc ( :defaults defaults))))
{:db (assoc (:db coeffects) :defaults defaults)}))) ;; returns effects map
```
So, by some magic, not yet revealed, LocalStore will be queried before
this handler runs and the required value from it will be placed into
`coeffects` under the key `:localstore` for the handler to use.
That process leaves the handler itself pure because it only sources data from arguments.
### Variations On A Theme
@ -350,16 +352,18 @@ and
{:db (assoc (:db context) :flag new-value)}))
```
Obviously the `-db` variation is simpler.
Obviously the `-db` variation is simpler and you'd use it whenever you
can. The `-fx` version is more flexible, so it will sometimes have its place.
### Summary
90% of the time, simple `-db` handlers are the tool to use.
90% of the time, simple `-db` handlers are the right tool to use.
But about 10% of the time, our handlers need additional inputs (coeffecs) or they need to
cause additional side-effects (effects). That's when you reach for `-fx` handlers.
To take the next step, we need now to shine a light on `interceptors` which provide
the mechanism by which event handlers are executed.
In the next tutorial, we'll shine a light on `interceptors` which are
the mechanism by which event handlers are executed. That knowledge will give us a springboard
to more deeply understand coeffects and effects. We'll soon be writing our own.

View File

@ -20,13 +20,13 @@ make side effects a noop in tests.
* [What Makes This Work?](#what-makes-this-work-)
* [Order Of Effects?](#order-of-effects-)
* [Noops](#noops)
- [Builtin Effects Handlers](#builtin-effects-handlers)
- [Builtin Effect Handlers](#builtin-effect-handlers)
+ [:dispatch-later](#-dispatch-later)
+ [:dispatch](#-dispatch)
+ [:dispatch-n](#-dispatch-n)
+ [:deregister-event-handler](#-deregister-event-handler)
+ [:db](#-db)
- [Other Available Effects](#other-available-effects)
- [External Effects](#external-effects)
## Effects
### Where Effects Come From
@ -53,7 +53,7 @@ the particular side effect required, and the `value` for that `key` provides
further information. The structure of the `value` varies with the side
effect itself.
Here's two instructions:
Here's the two instructions from the example above:
```cljs
{:db (assoc db :flag a) ;; side effect on app-db
:dispatch [:do-something-else 3]} ;; dispatch this event
@ -65,9 +65,8 @@ That `:db` `key` instructs that "app-db" should be reset to the
That `:dispatch` `key` instructs that an event should be
dispatched. The `value` is the vector to dispatch.
In addition to those two shown above, there's many other possible
effects, like for example `dispatch-n` whose value is
expected to be a collection of vectors to dispatch.
There's many other possible
effects, like for example `:dispatch-later` or `:set-local-store`.
And so on. And so on. Which brings us to a problem.
@ -78,14 +77,14 @@ possible effects is open ended.
What if you use Postgress and want an effect which issues mutating
queries? Or what if you want to send logs to Logentries.
Or write values to a LocalStore key. And what happens if your database is
Or write values to windows.location. And what happens if your database is
X, Y or Z?
The list is long and varied, with everyone using a different combination
of available effects.
So effect handling has to be extensible. You need to a way to define
your own side effects.
your own side effects.
### Extensible Side Effects
@ -104,7 +103,7 @@ __<1>__ the key for the effect. When an effects map contains
the key `:butterfly`, the registered function should be used to action it. <br>
__<2>__ a function which actions the side effect. It is called
with one argument - the value for this key, in the effects map.
with one argument - the value for this key, in the effects map.
So, if an event handler returned these effects:
```clj
@ -115,20 +114,23 @@ So, if an event handler returned these effects:
Then the function we registered for `:butterfly` would be called to handle that effect. And it would be called with the parameter "Flapping".
So, terminology:
- `:butterfly` is known as an "effect key"
- and the function registered is known as an "effect handler"
- `:butterfly` is an "effect key"
- and the function registered is an "effect handler"
### Writing An Effect Handler
A word of advice - make them as simple as possible, and then simplify them further. You don't want them containing any fancy logic.
A word of advice - make them as simple as possible, and then
simplify them further. You don't want them containing any fancy logic.
Why? Well, because they are all side-effecty they will be a pain to test rigorously. And the combination of fancy logic and limited testing always ends in tears.
Why? Well, because they are all side-effecty they will be a pain
to test rigorously. And the combination of fancy logic and limited
testing always ends in tears.
A second word of advice - when you create an effect handler,
you also have to design (and document!) the structure of the
`value` expected.
Realise that you are creating a nano DSL when designing this `value` and
When you do, realise that you are creating a nano DSL for `value` and try to
make it simple too. Resist tricky. Create as little cognitive overhead as possible
for the eventual readers of your effectful code.
@ -152,31 +154,36 @@ An effects map does not have to include the `effect key` `:db`.
It is perfectly okay to have effects which do not cause
an update to `app-db`.
In fact, it is perfectly okay for an event handler to return
an effects of `{}`. Slightly puzzling, but not a problem.
### What Makes This Work?
A silently inserted interceptor.
Whenever you register an event handler via __either__ `reg-event-db`
Whenever you register an event handler via __either__ `reg-event-db`
or `reg-event-fx`, an interceptor, cunningly named `do-effects`,
is inserted at the beginning of the chain.
or `reg-event-fx`, an interceptor, cunningly named `do-effects`, is inserted at the beginning of the chain.
So, if your event handler registration looked like this:
Example: if your event handler registration looked like this:
```clj
(reg-event-fx
:some-id
[debug (path :right)] ;; <-- two interceptors, apparently
(fn [coeffect]
{}) ;; <-- return effects here
{}) ;; <-- imagine returned effects here
```
You have actually registered with 3 interceptors:
While it might look like you have registered with 2 interceptors,
`reg-event-fx` will make it 3:
```clj
[do-effects debug (path :right)]
```
`do-effects` placement at the beginning of the interceptor chain means it's `:after` function is the final act
in the chain's first-forwards-and-then-backwards execution.
`do-effects` placement at the beginning of the interceptor chain means
it's `:after` function would be the final act when the chain is executed
(forwards and then backwards, as described in the Interceptor Tutorial).
In this final act, the `:after` function extracts `:effects` from `context`
and simply iterates across the key/value pairs it contains, calling the
@ -197,17 +204,19 @@ of the interceptor chain. It is only a few lines of code.
There isn't one.
`do-effects` does not currently allow you to control the order in
`do-effects` does not currently provide you with control over the order in
which side effects occur. The `:db` side effect
might happen before `:dispatch`, or not.
might happen before `:dispatch`, or not.
If you feel you need ordering, then please
open an issue and explain the usecase. It is the current absence of
good usecases which have lead to this not being implemented.
*Note:* if you feel you need ordering, then please
open an issue and explain the usecase. The current absence of
good usecases is the reason it isn't implemented. Give us some and
we'll revisit.
If ever ordering was needed, it might be handled via metadata on `:effects`. And
perhaps allow `reg-fx` to optionally take two functions:
- an effects pre-process fn <--- new
*Further Note:* if ever ordering was needed, it might be handled via
metadata on `:effects`. Also, perhaps allow `reg-fx` to optionally
take two functions:
- an effects pre-process fn <--- new. Takes `:effects` returns `:effects`
- the effects handler, as already discussed.
Anyway, these are all just possibilities. But not needed or implemented yet.
@ -233,7 +242,7 @@ XXX talk about reinstating:
## Builtin Effects Handlers
## Builtin Effect Handlers
#### :dispatch-later
@ -289,7 +298,7 @@ usage:
{:db {:key1 value1 key2 value2}}
```
## Other Available Effects
## External Effects
- https://github.com/Day8/re-frame-http-fx (GETs and POSTs)
- https://github.com/Day8/re-frame-forward-events-fx (slightly exotic)