From c1b9859941e899c2627d61b9c8c1424071610b32 Mon Sep 17 00:00:00 2001 From: Mike Thompson Date: Wed, 17 Aug 2016 18:53:14 +1000 Subject: [PATCH] Coeffect docs improved --- docs/coeffects.md | 119 +++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 53 deletions(-) diff --git a/docs/coeffects.md b/docs/coeffects.md index 6bfebf5..bc2822d 100644 --- a/docs/coeffects.md +++ b/docs/coeffects.md @@ -2,7 +2,7 @@ This tutorial explains `coeffects`. -It explains what they are, how they help, how they can be "injected", and how +It explains what they are, how they can be "injected", and how to manage them in tests. ## Table Of Contexts @@ -49,24 +49,27 @@ This handler obtains data directly from LocalStore: (assoc db :defaults val)))) ``` +This works, but there's a cost. + 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 it __only__ uses data from arguments. -To make this happen, our first change is to switch to +To make this happen, we first switch to using `reg-event-fx` (instead of `reg-event-db`). 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). +get two arguments, but the first is not `db`. Instead it +is an argument which 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 -`cofx`. Well, in addition we now want it to contain other useful data too. +Previous tutorials showed there's a `:db` key in `cofx`. We +now want `cofx` to have other keys and values: ```clj (reg-event-fx ;; note: -fx :load-defaults @@ -77,29 +80,29 @@ Previous tutorials have show us that we can obtain `:db` from ``` Notice how `cofx` magically contains a `:local-store` key with the -right value. How do we organise for this magic to happen? +right value. How do we make this magic happen? ### 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. -That pristine `context` value (containing a pristine `:coeffect` map) is then threaded +That pristine `context` value (containing a pristine `:coeffect` map) is threaded 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` magic happens. This is where +via their `:before` function. This is where `:coeffect` magic happens. This is how new keys can be added to `:coeffect`, so that later our handler magically finds the -right data (like `:local-store`) in its `cofx` argument. +right data (like `:local-store`) in its `cofx` argument. It is the Interceptors. ### Which Interceptors? If Interceptors put data in `:coeffect`, then we'll need to add the right ones to -our event handler when we register it. +when we register our event handler. -Something like this (this handler is the same as before, except for one addition): +Something like this (this handler is the same as before, except for one detail): ```clj (reg-event-fx :load-defaults @@ -110,17 +113,17 @@ Something like this (this handler is the same as before, except for one addition {:db (assoc db :defaults val))})) ``` -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 +So we've added one Interceptor. It will inject the right key/value pair +into `context's` `:coeffeects` which then goes on to be the first argument to our event handler (`cofx`) ### `inject-cofx` -`inject-cofx` is part of re-frame API. +`inject-cofx` is part of re-frame API. It is a function which returns an Interceptor whose `:before` function loads -a value into a `context's` `:coeffect` map. +a key/value pair 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. @@ -130,13 +133,13 @@ was "defaults-key" which was presumably the LocalStore key. ### More `inject-cofx` -Here's some other examples of its use: +Here's some other usage examples: - `(inject-cofx :random-int 10)` - `(inject-cofx :guid)` - `(inject-cofx :now)` -So, if I wanted to, I could create an event handler which has access to 3 coeffects: +I could create an event handler which has access to 3 coeffects: ```clj (reg-event-fx :some-id @@ -145,28 +148,30 @@ 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)) ``` -Adding 3 coeffects for the one handler is probably just showing off, and not generally necessary. +But that's probably just showing off, and not very useful. -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. +And so, to 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` -Allows you associate a`cofx-id` (like `:now` or `:local-store`) with a -handler function that injects the right data. +This function is also part of the re-frame API. + +It allows you associate a`cofx-id` (like `:now` or `:local-store`) with a +handler function that injects the right key/value pair. The function you register will be passed two arguments: - - a `:coeffects` map, and - - optionally, the additional value supplied -and it is expected to return a modified `:coeffects` map, presumably with an -added key and value. + - a `:coeffects` map (to which it should add key/value pair), and + - optionally, the additional value supplied to `inject-cofx` +and it is expected to return a modified `:coeffects` map. -### Examples +### Examples Of `reg-cofx` Above we wrote an event handler that wanted `:now` data to be available. Here is how a handler could be registered for `:now`: ```clj -(reg-cofx ;; uses this new registration function +(reg-cofx ;; registration function :now ;; what cofx-id are we registering (fn [coeffects _] ;; second parameter not used in this case (assoc coeffects :now (js.Date.)))) ;; add :now key, with value @@ -186,6 +191,36 @@ And there's this example: With these two registrations in place, I can now use `(inject-cofx :now)` and `(inject-cofx :local-store "blah")` in an effect handler's interceptor chain. + +### Secret Interceptors + +In a previous tutorial we learned that `reg-events-db` +and `reg-events-fx` add Interceptors to front of any chain +during registration.We found they inserted an Interceptor called `do-fx`. + +I can now reveal that +they also add `(inject-cofx :db)` at the front of each chain. + +Guess what that injects into the `:coeffects` of every event handler? + +Okay, so that was the last surprise. Now you know everything. + +### Testing + +During testing, you may want to mock certain coeffets. + +You may, for example, want to test that an event handler works +using a specific `random number`, not a true random number. + +In your test, you'd mock out the cofx handler: +``` +(reg-cofx + :random-int + (fn [coeffects _] + (assoc coeffects :random-int 5))) ;; 5 is not very random +``` + + ### The 5 Point Summary In note form: @@ -193,29 +228,7 @@ In note form: 1. Event handlers should only source data from their arguments 2. We want to "inject" required data into the first, cofx argument 3. We use the `(inject-cofx :key)` interceptor in registration of the event handler - 4. It will the registered cofx handler for that `:key` to do the injection + 4. It will look up the registered cofx handler for that `:key` to do the injection 5. We must have previously registered a cofx handler via `reg-cofx` - -### Secret Interceptors - -In a previous tutorial we learned that `reg-events-db` -and `reg-events-fx` add Interceptors to front of any chain during registration. - -We found they inserted an Interceptor called `do-fx`. I can now reveal that -they also add `(inject-cofx :db)` at the front of each chain. - -Guess what that injects into the `:coeffects` of every event handler? - -Okay, so that was the last surprise. Now you know everything. Hopefully -the pizzle pieces fit nicely together - -### Testing - -During testing, you may want to stub out certain coeffets. - -You may, for example, want to test that an event handler works -using a specific `random number`. - -