More docs review

This commit is contained in:
Mike Thompson 2016-08-17 00:08:30 +10:00
parent 3f0679f396
commit fead1b04b3
4 changed files with 101 additions and 118 deletions

View File

@ -5,9 +5,6 @@ Yes, a surprising claim.
## Table Of Contents
- [Effectful Handlers](#effectful-handlers)
- [Table Of Contents](#table-of-contents)
- [Effects](#effects)
* [Events Happen](#events-happen)
* [Handling The Happening](#handling-the-happening)
* [Your Handling](#your-handling)
@ -41,16 +38,16 @@ message arrives on a websocket.
### Handling The Happening
Once dispatched, an event must be "handled". It must be processed, actioned.
Once dispatched, an event must be "handled" - which means it must be processed or actioned.
Events are mutative by nature. If your application is in one state before an
event is processed, it will be in a different state afterwards.
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.
websocket message. Without mutation, an app would just sits there, stuck.
State change is how the application "moves forward" - how it does its job. Useful!
State change is how an 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.
@ -63,7 +60,7 @@ 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 ;; <-- call this to register handlers
(re-frame.core/reg-event-db ;; <-- call this to register a handler
:set-flag ;; this is an event id
(fn [db [_ new-value] ;; this function does the handling
(assoc db :flag new-value)))
@ -136,7 +133,7 @@ replay, inserting extra events into it, etc, which ruins the process.
### The Other Problem
And there's another purity problem:
And there's another kind of purity problem:
```clj
(reg-event-db
:load-localstore
@ -145,21 +142,20 @@ And there's another purity problem:
(assoc db :defaults val))))
```
It sources data from LocalStore.
You'll notice it sources data from LocalStore.
So this handler has no side effect - it doesn't need to change the world - __but__ it does
need to source data from somewhere other than its arguments - from somewhere
outside of app-db or the event.
Although this handler has no side effect - it doesn't need to change the world - it does
need to source data from somewhere other than its arguments, which, in turn, means it isn't pure.
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** - 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))
- **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.
We'll need a solution for both situations.
### Why Does This Happen?
@ -169,8 +165,8 @@ They have to implement the control logic of your re-frame app, and
that means dealing with the outside, mutative world of servers, databases,
windows.location, LocalStore, cookies, etc.
There's just no getting away from living in a mutative world, er,
which sounds ominous. Is that it? Are we doomed to impurity?
There's just no getting away from living in a mutative world,
which sounds pretty 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
@ -178,7 +174,7 @@ which *do side-effects*, we'll instead get them to *cause side-effects*.
### Doing vs Causing
Above, I proudly claimed that this `fn` event handler was pure:
Above, I proudly claimed that this event handler was pure:
```clj
(reg-event-db
:my-event
@ -245,7 +241,7 @@ Here is an impure, side effecting handler:
(reg-event-db
:my-event
(fn [db [_ a]]
(dispatch [:do-something-else 3]) ;; Eeek, side-effect
(dispatch [:do-something-else 3]) ;; <-- Eeek, side-effect
(assoc db :flag true)))
```
@ -284,7 +280,7 @@ The impure way:
(assoc db :flag true)))
```
the pure, descriptive way:
the pure, descriptive alternative:
```clj
(reg-event-fx
:my-event
@ -315,17 +311,18 @@ It is now time to name that first argument:
```clj
(reg-event-fx
:my-event
(fn [coeffects event] ;; <--- thy name be coeefects
(fn [cofx event] ;; <--- thy name be cofx
{ ... }))
```
When you use the `-fx` form of registration, the first argument of your handler will be a coeffects map.
When you use the `-fx` form of registration, the first argument of your handler will be a map of coeffects which we name `cofx`.
In that map will be the complete set of "inputs" required by your function. The complete
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.
This is will explained 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 of `app-db`.
One of the keys in `cofx` will likely be `:db` and that will be the value of `app-db`.
Remember this impure handler from before:
```clj
@ -340,14 +337,14 @@ We'd now rewrite that as a pure handler, like this:
```clj
(reg-event-fx ;; notice the -fx
:load-localstore
(fn [coeffect _] ;; coeffect is a map containing inputs
(let [defaults (:defaults-key coeffect)] ;; <-- use it here
{:db (assoc (:db coeffects) :defaults defaults)}))) ;; returns effects map
(fn [cofx _] ;; cofx is a map containing inputs
(let [defaults (:defaults-key cofx)] ;; <-- use it here
{:db (assoc (:db cofx) :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.
`cofx` under the key `:localstore` for the handler to use.
That process leaves the handler itself pure because it only sources data from arguments.
@ -368,7 +365,7 @@ Just to be clear, the following two handlers achieve exactly the same thing:
(fn [db [_ new-value]
(assoc db :flag new-value)))
```
and
vs
```clj
(reg-event-fx
:set-flag

View File

@ -12,8 +12,6 @@ make side effects a noop in event replays.
## Table Of Contexts
- [Introduction](#introduction)
- [Table Of Contexts](#table-of-contexts)
- [Effects](#effects)
* [Where Effects Come From](#where-effects-come-from)
* [The Effects Map](#the-effects-map)
@ -55,12 +53,12 @@ An effects map contains instructions.
Each key/value pair in the map is one instruction - the `key` uniquely identifies
the particular side effect required, and the `value` for that `key` provides
further data. The structure of the `value` varies on a per side-effect basis.
further data. The structure of the `value` is different for each side-effect.
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
{:db (assoc db :flag a) ;; side effect on app-db
:dispatch [:do-something-else 3]} ;; dispatch this event
```
That `:db` `key` instructs that "app-db" should be `reset!` to the
@ -84,8 +82,8 @@ queries? Or what if you want to send logs to Logentries or metrics to DataDog.
Or write values to windows.location. And what happens if your database is
X, Y or Z?
The list of effects is long and varied, with everyone using a
different combination of them.
The list of effects is long and varied, with everyone needing to use a
different combination.
So effect handling has to be extensible. You need to a way to define
your own side effects.
@ -104,11 +102,11 @@ Use it like this:
))
```
__<1>__ the key for the effect. When an effects map contains
the key `:butterfly`, the registered function will be used to action it. <br>
__<1>__ the key for the effect. When later an effects map contains
the key `:butterfly`, the function we are registering will be used to action it. <br>
__<2>__ the function which actions the side effect. It will be called
with one argument - the value for this key, in the effects map.
__<2>__ the function which actions the side effect. Later, it will be called
with one argument - the value in the effects map, for this key.
So, if an event handler returned these effects:
```clj
@ -179,7 +177,7 @@ Example: if your event handler registration looked like this:
(reg-event-fx
:some-id
[debug (path :right)] ;; <-- two interceptors, apparently
(fn [coeffect]
(fn [cofx _]
{}) ;; <-- imagine returned effects here
```
@ -240,7 +238,7 @@ Some effects have no associated data:
{:exit-fullscreen nil})) ;; <--- no data, use a nil
```
So in these cases, although it looks odd, just supply `nil` as the value for this key.
In these cases, although it looks odd, just supply `nil` as the value for this key.
The associated effect handler would look like:
```clj
@ -264,11 +262,6 @@ Want to stub out the `:dispatch` effect? Do this:
(fn [_] )) ;; a noop
```
XXX talk about reinstating:
- capture return
- XXX new feature?
## Builtin Effect Handlers
#### :dispatch-later

View File

@ -4,8 +4,6 @@ This is an interceptors tutorial.
## Table Of Contents
- [Introduction](#introduction)
- [Table Of Contents](#table-of-contents)
- [Interceptors](#interceptors)
* [Why Interceptors?](#why-interceptors-)
* [What Do Interceptors Do?](#what-do-interceptors-do-)
@ -14,12 +12,12 @@ This is an interceptors tutorial.
* [Show Me](#show-me)
* [Handlers Are Interceptors Too](#handlers-are-interceptors-too)
- [Executing A Chain](#executing-a-chain)
* [The Links](#the-links)
* [The Links Of The Chain](#the-links-of-the-chain)
* [What Is Context?](#what-is-context-)
* [Self Modifing](#self-modifing)
* [Self Modifying](#self-modifying)
* [Credit](#credit)
* [Write An Interceptor](#write-an-interceptor)
* [Wrapping Handlers](#wrapping-handlers)
+ [Wrapping Handlers](#wrapping-handlers)
- [Summary](#summary)
- [Appendix](#appendix)
* [The Builtin Interceptors](#the-builtin-interceptors)
@ -35,11 +33,10 @@ Interceptors can look after "cross-cutting" concerns like undo, tracing and vali
They help us to factor out commonality, hide complexity and introduce further steps into the "Derived Data,
Flowing" story promoted by re-frame.
So, you'll want to use Interceptors - they're helpful.
So, you'll want to use Interceptors because they solve problems, and help you to write nice code.
__Second__, under the covers, Interceptors are the means by which
event handlers are executed (when you `dispatch`). You'll
want to understand how that happens.
__Second__, under the covers, Interceptors provide the mechanism by which
event handlers are executed (when you `dispatch`). You they are central concept.
### What Do Interceptors Do?
@ -92,8 +89,8 @@ concept, right there.
### Show Me
You can provide a chain of interceptors when
you register an event handler.
At the time when you register an event handler, you can provide an
chain of interceptors too.
Using a 3-arity registration function:
```clj
@ -111,7 +108,7 @@ Using a 3-arity registration function:
You might see that registration above as associating `:some-id` with two things: (1) a chain of interceptors
and (2) a handler.
Except, the handler is turned into an interceptor too. (We'll see how later)
Except, the handler is turned into an interceptor too. (We'll see how shortly)
So `:some-id` is only associated with one thing: a 3-chain of interceptors,
with the handler wrapped in an interceptor and put on the end of the other two.
@ -121,16 +118,16 @@ and inserts its own interceptors
(which do useful things) at the front (more on this soon too),
so ACTUALLY, there's about 5 interceptors in the chain.
So, ultimately, that event registration associates the event id `some-id`
So, ultimately, that event registration associates the event id `:some-id`
with a chain of interceptors.
Later, when a `dispatch` for `:some-id` is done, that 5-chain of
Later, when a `(dispatch [:some-id ...])` happens, that 5-chain of
interceptors will be "executed". And that's how events get handled.
## Executing A Chain
### The Links
### The Links Of The Chain
Each interceptor has this form:
```clj
@ -177,7 +174,7 @@ respectively.
`:coeffects` will contain the inputs required by the event handler
(sitting presumably on the end of the chain). So that's
data like the `:event` being processed, and the initial state of `db`. These are .
data like the `:event` being processed, and the initial state of `db`.
The handler-returned side effects are put into `:effects` including,
but not limited to, new values for `db`.
@ -185,16 +182,16 @@ but not limited to, new values for `db`.
The first few interceptors in a chain (inserted by `reg-event-db`)
have `:before` functions which __prime__ the `:coeffects`
by adding in `:event`, and `:db`. Of course, other interceptors can
add further to `:coeffect`. Perhaps the event handler needs
add further to `:coeffects`. Perhaps the event handler needs
data from localstore, or a random number, or a
DataScript connection. Interceptors can build up the coeffect, via their
DataScript connection. Interceptors can build up `:coeffects`, via their
`:before`.
Equally, some interceptors in the chain will have `:after` functions
which process the side effects accumulated into `:effects`
including but, not limited to, updates to app-db.
### Self Modifing
### Self Modifying
Through both stages (before and after), `context` contains a `:queue`
of interceptors yet to be processed, and a `:stack` of interceptors
@ -238,7 +235,7 @@ passive aggressive, understated thing it has going on!! Co-workers
have said I'm "being overly sensitive", perhaps even horizontalist, but
you can see it, right? Of course you can.
What a relief it would be to rid of it, but how? We'll write an interceptor: `trim-event`
What a relief it would be to get rid of it, but how? We'll write an interceptor: `trim-event`
Once we have written `trim-event`, our registration will change to look like this:
```clj
@ -274,18 +271,18 @@ And, here it is:
As you read this, look back to what a `context` looks like.
Notes:
1. We use `->interceptor` to create an interceptor (but it just a map)
1. We use `->interceptor` to create an interceptor (which is just a map)
2. Our interceptor only has a `:before` function
3. Our `:before` is given `context`. It modifies it and returns it.
4. There is no `:after` for this Interceptor. It has nothing to do
with the backwards processing flow of effects. It is concerned only
with coeffects in the forward flow.
with the backwards processing flow of `:effects`. It is concerned only
with `:coeffects` in the forward flow.
####Wrapping Handlers
#### Wrapping Handlers
We're going well. Let's do an advanced wrapping.
How would you wrap a handler in an interceptor?
How would you wrap a handler in an Interceptor?
There's two kinds of handler:
- the `-db` variety registered by `reg-event-db`
@ -297,7 +294,7 @@ Let's do a `-db` variety. This is what a `-db` handler looks like:
(assoc db :flag true)) ;; returns a new db
```
And here is a function which turns a given handler into an interceptor:
And here is a function which turns a `-db` handler into an interceptor:
```clj
(defn db-handler->interceptor
[db-handler-fn]
@ -315,7 +312,7 @@ And here is a function which turns a given handler into an interceptor:
In this tutorial, we've learned:
__1.__ When you register an event handler, you can supply a collection of interceptors:
```
```clj
(reg-event-db
:some-id
[in1 in2] ;; <--- a chain of 2 interceptors
@ -323,12 +320,16 @@ __1.__ When you register an event handler, you can supply a collection of interc
....)))
```
__2.__ When you registering an event handler, you are associating an event id with a chain of interceptors including:
- the ones your supply
__2.__ When you are registering an event handler, you are associating an event id with a chain of interceptors including:
- the ones your supply (optional)
- an extra one on the end, which wraps the handler itself
- a couple at the beginning of the chain, put there by the `reg-event-db` or `reg-event-fx`.
__3.__ Interceptors do interesting things:
__3.__ An Interceptor Chain is executed in two stages. First a forwards sweep in which
all `:before` functions are called, and then second, a backwards sweep in which the
`:after` functions are called. A `context` will be threaded through all these calls.
__4.__ Interceptors do interesting things:
- add to coeffects (data inputs to the handler)
- process side effects (returned by a handler)
- produce logs

View File

@ -7,19 +7,16 @@ to manage them in tests.
## Table Of Contexts
- [Introduction](#introduction)
- [Table Of Contexts](#table-of-contexts)
- [Coeffects](#coeffects)
* [What Are They?](#what-are-they-)
* [An Example](#an-example)
* [Let's Fix It](#let-s-fix-it)
* [How We Want It](#how-we-want-it)
* [How Are Coeffect Babies Made?](#how-are-coeffect-babies-made-)
* [So, Next Step](#so--next-step)
* [`coeffect` the function](#-coeffect--the-function)
* [Other Example Uses of `coeffects`](#other-example-uses-of--coeffects-)
* [`inject-cofx`](#-inject-cofx-)
* [More `inject-cofx`](#more--inject-cofx-)
* [Meet `reg-cofx`](#meet--reg-cofx-)
* [Examples](#examples)
* [The 4 Point Summary](#the-4-point-summary)
* [The 5 Point Summary](#the-5-point-summary)
* [Secret Interceptors](#secret-interceptors)
* [Testing](#testing)
@ -43,7 +40,7 @@ DataScript connection.
### An Example
This handler obtains data directly from LocalStore
This handler obtains data directly from LocalStore:
```clj
(reg-event-db
:load-defaults
@ -52,22 +49,24 @@ This handler obtains data directly from LocalStore
(assoc db :defaults val))))
```
Because it has accessed LocalStore, this event handler is not
Because it has directly accessed LocalStore, this event handler is not
pure, and impure functions cause well-documented paper cuts.
### How We Want It
Our goal in this tutorial is to rewrite this event handler so
that data _only_ comes from the arguments.
that data _only_ comes from the arguments.
Our first change is to start using `reg-event-fx` (instead of
`reg-event-db`).
To make this happen, our first change is to switch to
using `reg-event-fx` (instead of `reg-event-db`).
Then we'll seek to have ALL the necessary extra data available in the
first argument, typically called `coeffects`.
Event handlers registered via `reg-event-fx` are slightly
different to those registered via `reg-event-fx`. `-fx` handlers
get two arguments, but the first is not `db`. Instead it is an argument which, in
this tutorial, we will call `cofx` (that's a nice distinct name which will aid communication).
Previous tutorials have show us that we can obtain `:db` from
`coeffects`. Well, not we want it to contain other useful data too.
`cofx`. Well, in addition we now want it to contain other useful data too.
```clj
(reg-event-fx ;; note: -fx
:load-defaults
@ -77,36 +76,30 @@ Previous tutorials have show us that we can obtain `:db` from
{:db (assoc db :defaults val))})) ;; returns an effect
```
If we can find a way to achieve this, then we are back to
writing pure event handlers.
But what must we do to data into cofx? How do we organise for it
to contain a `:local-store` key, with the right value?
Notice how `cofx` magically contains a `:local-store` key with the
right value. How do we organise for this magic to happen?
### How Are Coeffect Babies Made?
Well, when two coeffects love each other very much ... no, stop ... this
is a G-rated framework. Instead ...
### Abracadabra
Each time an event handler is executed, a brand new `context` is created, and within that
`context` is a brand new `:coeffect` map, which is initially totally empty.
`context` is a brand new `:coeffect` map, which is initially totally empty.
That pristine `context` value (containing a pristine `:coeffect` map) is then threaded
through a chain of Interceptors before it is finally handled to our event handler
which will be sitting on the end of chain, itself wrapped up in an interceptor. We know
through a chain of Interceptors before it is finally handled to our event handler,
sitting on the end of a chain, itself wrapped up in an interceptor. We know
this story well from a previous tutorial.
So, all members of the Interceptor chain have the opportunity to add to `:coeffects`
via their `:before` function. This is where `:coeffect` gets made. This is where
new keys are added to `:coeffect`, so that later our handler magically finds the
right data in its parameter.
via their `:before` function. This is where `:coeffect` magic happens. This is where
new keys can be added to `:coeffect`, so that later our handler magically finds the
right data (like `:local-store`) in its `cofx` argument.
### So, Next Step
### Which Interceptors?
If Interceptors put data in `:coeffect`, then we'd better put the right ones on
our handler when we register it.
If Interceptors put data in `:coeffect`, then we'll need to add the right ones to
our event handler when we register it.
This handler is the same as before, except for one addition:
Something like this (this handler is the same as before, except for one addition):
```clj
(reg-event-fx
:load-defaults
@ -117,8 +110,9 @@ This handler is the same as before, except for one addition:
{:db (assoc db :defaults val))}))
```
So we've added one Interceptor. It will inject the right value into `context's` `:coeffeects`
and that `:coeffects` ends up being the first parameter to our handler.
So we've added one Interceptor and it will do the magic. It will inject the right
value into `context's` `:coeffeects` which then goes on to be the first argument
to our event handler (`cofx`)
### `inject-cofx`
@ -128,13 +122,11 @@ and that `:coeffects` ends up being the first parameter to our handler.
It is a function which returns an Interceptor whose `:before` function loads
a value into a `context's` `:coeffect` map.
`inject-cofx` takes either one or two arguments. The first is always the `id` of the coeffect
required (called a `cofx-id`). The 2nd is an optional addition value.
.
So, in the case above, the `cofx-id` was `:local-store` and the additional value
was "defaults-key" which was presumable the place to look in LocalStore
was "defaults-key" which was presumably the LocalStore key.
### More `inject-cofx`
@ -153,9 +145,9 @@ So, if I wanted to, I could create an event handler which has access to 3 coeff
... in here I can access cofx's keys :now :local-store and :random-int))
```
Creating 3 coeffects for the one handler is probably just showing off, and not generally necessary.
Adding 3 coeffects for the one handler is probably just showing off, and not generally necessary.
And so to the final piece in the puzzle. How does `inject-cofx` know what to do when
And so, the final piece in the puzzle: how does `inject-cofx` know what to do when
it is given `:now` or `:local-store` ? Each `cofx-id` requires a different action.
### Meet `reg-cofx`