re-frame/docs/MentalModelOmnibus.md

402 lines
17 KiB
Markdown
Raw Normal View History

2017-07-16 00:30:57 +00:00
> In a rush? You can skip this tutorial page on a first pass. <br>
> It is quite abstract and it won't directly help you write re-frame code.
> On the other hand, it will considerably deepen your understanding
> of what re-frame is about, so remember to cycle back and read it later.<br>
2016-12-22 09:45:20 +00:00
> Next page: [Effectful Handlers](EffectfulHandlers.md)
2016-10-22 03:56:04 +00:00
## Mental Model Omnibus
<img height="450px" align="right" src="/images/mental-model-omnibus.jpg?raw=true">
2016-12-11 12:46:55 +00:00
2016-12-28 23:01:23 +00:00
> All models are wrong, but some are useful
2016-12-14 22:27:42 +00:00
The re-frame tutorials initially focus on the **domino
2016-12-04 20:59:11 +00:00
narrative**. The goal is to efficiently explain the mechanics of re-frame,
2016-12-17 06:28:26 +00:00
and to get you reading and writing code ASAP.
2016-10-25 06:22:49 +00:00
2016-12-14 22:27:42 +00:00
But **there are other perspectives** on re-frame
2016-12-13 21:57:28 +00:00
which will deepen your understanding.
2016-10-22 03:56:04 +00:00
2016-12-11 12:46:55 +00:00
This tutorial is a tour of these ideas, justifications and insights.
2017-02-02 19:58:58 +00:00
It is a little rambling, but I'm hoping it will deliver for you
2016-12-11 12:46:55 +00:00
at least one "Aaaah, I see" moment before the end.
2016-12-04 20:59:11 +00:00
2016-12-28 23:01:23 +00:00
> If a factory is torn down but the rationality which produced it is
left standing, then that rationality will simply produce another
factory. If a revolution destroys a government, but the systematic
patterns of thought that produced that government are left intact,
then those patterns will repeat themselves. <br>
> -- Robert Pirsig, Zen and the Art of Motorcycle Maintenance
2016-12-22 09:45:20 +00:00
2016-12-14 22:27:42 +00:00
2016-10-25 06:22:49 +00:00
## What is the problem?
2016-10-21 09:40:32 +00:00
First, we decided to build our SPA apps with ClojureScript, then we
2016-12-17 06:28:26 +00:00
chose [Reagent], then we had a problem. It was mid 2014.
2016-10-21 09:40:32 +00:00
For all its considerable brilliance, Reagent (+ React)
delivers only the 'V' part of a traditional MVC framework.
2016-12-03 10:44:06 +00:00
But apps involve much more than V. We build quite complicated
2017-02-02 19:58:58 +00:00
SPAs which can run to 50K lines of code. So, I wanted to know:
2016-12-03 20:57:31 +00:00
where does the control logic go? How is state stored & manipulated? etc.
2016-10-21 09:40:32 +00:00
2017-02-02 19:58:58 +00:00
We read up on [Pedestal App], [Flux],
2016-12-17 06:28:26 +00:00
[Hoplon], [Om], early [Elm], etc., and re-frame is the architecture that
2016-12-03 10:44:06 +00:00
emerged. Since then, we've tried to keep an eye on further developments like the
2016-10-21 09:40:32 +00:00
Elm Architecture, Om.Next, BEST, Cycle.js, Redux, etc. They have taught us much
although we have often made different choices.
2016-12-02 12:46:06 +00:00
re-frame does have parts which correspond to M, V, and C, but they aren't objects.
2016-10-21 09:40:32 +00:00
It is sufficiently different in nature
from (traditional, Smalltalk) MVC that calling it MVC would be confusing. I'd
love an alternative.
2016-10-22 03:56:04 +00:00
Perhaps it is a RAVES framework - Reactive-Atom Views Event
2016-10-21 09:40:32 +00:00
Subscription framework (I love the smell of acronym in the morning).
Or, if we distill to pure essence, `DDATWD` - Derived Data All The Way Down.
*TODO:* get acronym down to 3 chars! Get an image of stacked Turtles for `DDATWD`
insider's joke, conference T-Shirt.
## Guiding Philosophy
2016-12-17 06:28:26 +00:00
__First__, above all, we believe in the one true [Dan Holmsand], creator of Reagent, and
his divine instrument: the `ratom`. We genuflect towards Sweden once a day.
2016-10-21 09:40:32 +00:00
__Second__, we believe in ClojureScript, immutable data and the process of building
a system out of pure functions.
2016-12-03 10:44:06 +00:00
__Third__, we believe in the primacy of data, for the reasons described in
2016-12-03 20:57:31 +00:00
the main README. re-frame has a data oriented, functional architecture.
2016-10-21 09:40:32 +00:00
2017-02-02 19:58:58 +00:00
__Fourth__, we believe that Reactive Programming is one honking good idea.
How did we ever live without it? It is a quite beautiful solution to one half of re-frame's
data conveyance needs, **but** we're cautious about taking it too far - as far as, say, cycle.js.
2016-10-21 09:40:32 +00:00
It doesn't take over everything in re-frame - it just does part of the job.
2017-02-02 19:58:58 +00:00
__Finally__, a long time ago in a galaxy far far away, I was lucky enough to program in Eiffel
2017-01-02 21:20:59 +00:00
where I was exposed to the idea of [command-query separation](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation).
2017-02-02 19:58:58 +00:00
The modern rendering of this idea is CQRS ([see resources here](http://www.baeldung.com/cqrs-event-sourced-architecture-resources)).
But, even today, we still see read/write `cursors` and two-way data binding being promoted as a good thing.
Please, just say no. We already know where that goes. As your programs get bigger, the use of these two-way constructs
will encourage control logic into all the
wrong places and you'll end up with a tire-fire of an Architecture. <br>
2017-01-02 21:20:59 +00:00
Sincerely, The Self-appointed President of the Cursor Skeptic's Society.
2016-10-21 05:08:16 +00:00
2017-07-16 00:08:03 +00:00
## On DSLs and Machines
2017-07-14 05:43:58 +00:00
2017-07-16 00:08:03 +00:00
`Events` are cardinal to re-frame - they're a fundamental organising principle.
2017-07-14 05:43:58 +00:00
2017-07-16 00:47:08 +00:00
Each re-frame app will have a different set of `events` and your job is
2017-07-15 04:35:28 +00:00
to design exactly the right ones for any given app you build. These `events`
2017-07-16 00:47:08 +00:00
will model "intent" - generally the user's. They will be the
"language of the system" and will provide the eloquence.
2017-07-14 05:43:58 +00:00
And they are data.
2017-07-15 04:35:28 +00:00
Imagine we created a drawing application. And then we allowed
2017-07-16 09:49:40 +00:00
someone to use our application and, as they did, we captured,
into a collection, the events caused by that user's actions
(button clicks, drags, key presses, etc).
2017-07-15 04:35:28 +00:00
The collection of events might look like this:
2017-07-14 05:43:58 +00:00
```cljs
2017-07-15 04:35:28 +00:00
(def collected-events
2017-07-14 05:43:58 +00:00
[
[:clear]
[:new :triangle 1 2 3]
[:select-object 23]
2017-07-16 00:47:08 +00:00
[:rename "a better name"]
[:delete-selection]
2017-07-14 05:43:58 +00:00
....
])
```
2017-07-16 09:49:40 +00:00
Now, as an aside, consider the following assembly instructions:
2017-07-14 05:43:58 +00:00
```asm
mov eax, ebx
sub eax, 216
mov BYTE PTR [ebx], 2
```
2017-07-16 09:49:40 +00:00
Assembly instructions are represented as data, right? Data which
happens to be "executable" by the right machine - an x86 machine in the case above.
2017-07-14 05:43:58 +00:00
2017-07-16 00:08:03 +00:00
I'd like you to now look back at that collection of events and view it in the
2017-07-16 00:47:08 +00:00
same way - data instructions which can be executed - by the right machine.
2017-07-14 05:43:58 +00:00
2017-07-15 04:35:28 +00:00
Wait. What machine? Well, the Event Handlers you register collectively implement
2017-07-16 09:49:40 +00:00
the "machine" on which these instructions execute. When you register
a new event handler using `reg-event-db`,
it is like you are adding to the "instruction set" of the "machine".
In re-frame's README, near the top, I claimed that it had a
Data Oriented Design. Typically, that claim means you "program" in a data
structure of a certain format (Domain specific language),
which is then "executed" by an interpreter.
Take hiccup as an example. It is a DSL for describing DOM.
You program by supplying a data structure in a particular,
known format (the DSL) and Reagent acts as the
"interpreter" which executes that "language":
```
[:div {:font-size 12} "Hello"] ;; a data structure
```
2017-07-14 05:43:58 +00:00
2017-07-16 09:49:40 +00:00
Back to re-frame. It requires that YOU design events which
combine into a DSL for your app
and, at the same time, it asks YOU to provide an interpreter for
each instruction in that DSL. When your re-frame application runs,
it is just executing a "program" (collection of events)
dynamically created by the user's event-causing actions.
In summary:
2017-07-15 04:35:28 +00:00
- Events are the assembly language of your app.
2017-07-16 09:49:40 +00:00
- These instructions collectively form a Domain Specific Language (DSL). The language of your system.
2017-07-15 04:35:28 +00:00
- These instructions are data.
- One instruction after another gets executed by your functioning app.
- The Event Handlers you register collectively implement the "machine" on which this DSL executes.
2017-07-16 00:30:57 +00:00
On the subject of DSLs, watch James Reeves' excellent talk (video): [Transparency through data](https://www.youtube.com/watch?v=zznwKCifC1A)
2017-07-14 05:43:58 +00:00
2016-10-22 03:56:04 +00:00
## It does Event Sourcing
2016-10-21 09:40:32 +00:00
2016-10-26 20:05:05 +00:00
How did that error happen, you puzzle, shaking your head ruefully?
What did the user do immediately prior? What
state was the app in that this event was so problematic?
2016-10-21 09:40:32 +00:00
2016-10-22 03:56:04 +00:00
To debug, you need to know this information:
2016-10-21 09:40:32 +00:00
1. the state of the app immediately before the exception
2016-10-26 20:05:05 +00:00
2. What final `event` then caused your app to error
2016-10-21 09:40:32 +00:00
Well, with re-frame you need to record (have available):
2016-10-25 06:22:49 +00:00
1. A recent checkpoint of the application state in `app-db` (perhaps the initial state)
2016-10-26 20:05:05 +00:00
2. all the events `dispatch`ed since the last checkpoint, up to the point where the error occurred
2016-10-21 09:40:32 +00:00
Note: that's all just data. **Pure, lovely loggable data.**
2016-10-26 20:05:05 +00:00
If you have that data, then you can reproduce the error.
2016-10-21 09:40:32 +00:00
2017-02-02 19:58:58 +00:00
re-frame allows you to time travel, even in a production setting.
2017-07-16 00:30:57 +00:00
To find the bug, install the "checkpoint" state into `app-db`
2016-12-17 06:28:26 +00:00
and then "play forward" through the collection of dispatched events.
2016-10-21 09:40:32 +00:00
The only way the app "moves forwards" is via events. "Replaying events" moves you
2016-10-26 20:05:05 +00:00
step by step towards the error causing problem.
2016-10-21 09:40:32 +00:00
This is perfect for debugging assuming, of course, you are in a position to capture
2016-12-03 20:57:31 +00:00
a checkpoint of `app-db`, and the events since then.
2016-10-21 09:40:32 +00:00
Here's Martin Fowler's [description of Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html).
2017-02-02 19:58:58 +00:00
## It does a reduce
2016-10-21 09:40:32 +00:00
2016-12-11 12:46:55 +00:00
2017-02-02 19:58:58 +00:00
Here's an interesting way of thinking about the re-frame
2016-10-22 03:56:04 +00:00
data flow ...
2016-10-21 09:40:32 +00:00
2017-02-02 19:58:58 +00:00
**First**, imagine that all the events ever dispatched in a
2016-10-26 20:05:05 +00:00
certain running app were stored in a collection (yes, event sourcing again).
2017-02-02 19:58:58 +00:00
So, if when the app started, the user clicked on button X
the first item in this collection would be the event
generated by that button, and then, if next the user moved
a slider, the associated event would be the next item in
2016-10-21 09:40:32 +00:00
the collection, and so on and so on. We'd end up with a
collection of event vectors.
2017-02-02 19:58:58 +00:00
**Second**, remind yourself that the `combining function`
2016-12-03 10:44:06 +00:00
of a `reduce` takes two arguments:
2016-10-21 09:40:32 +00:00
1. the current state of the reduction and
2016-12-04 20:59:11 +00:00
2. the next collection member to fold in
2016-10-21 09:40:32 +00:00
2016-12-03 10:44:06 +00:00
Then notice that `reg-event-db` event handlers take two arguments also:
2016-10-26 20:05:05 +00:00
1. `db` - the current state of `app-db`
2. `v` - the next event to fold in
2016-10-21 09:40:32 +00:00
2016-10-26 20:05:05 +00:00
Interesting. That's the same as a `combining function` in a `reduce`!!
2016-10-21 09:40:32 +00:00
2017-07-16 00:30:57 +00:00
So, now we can introduce the new mental model: at any point in time,
2016-10-21 09:40:32 +00:00
the value in `app-db` is the result of performing a `reduce` over
2017-02-02 19:58:58 +00:00
the entire `collection` of events dispatched in the app up until
2016-10-22 03:56:04 +00:00
that time. The combining function for this reduce is the set of event handlers.
2016-10-21 09:40:32 +00:00
2017-02-02 19:58:58 +00:00
It is almost like `app-db` is the temporary place where this
2016-10-21 09:40:32 +00:00
imagined `perpetual reduce` stores its on-going reduction.
2017-02-02 19:58:58 +00:00
Now, in the general case, this perspective breaks down a bit,
because of `reg-event-fx` (has `-fx` on the end, not `-db`) which
2016-10-25 06:22:49 +00:00
allows:
2017-02-02 19:58:58 +00:00
1. Event handlers to produce `effects` beyond just application state
2016-10-22 03:56:04 +00:00
changes.
2017-02-02 19:58:58 +00:00
2. Event handlers to have `coeffects` (arguments) in addition to `db` and `v`.
2016-10-22 03:56:04 +00:00
2017-02-02 19:58:58 +00:00
But, even if it isn't the full picture, it is a very useful
2016-12-03 20:57:31 +00:00
and interesting mental model. We were first exposed to this idea
2017-02-02 19:58:58 +00:00
via Elm's early use of `foldp` (fold from the past), which was later enshrined in the
2016-10-22 03:56:04 +00:00
Elm Architecture.
2016-10-21 09:40:32 +00:00
2016-12-04 20:59:11 +00:00
## Derived Data All The Way Down
For the love of all that is good, please watch this terrific
2017-02-02 19:58:58 +00:00
[StrangeLoop presentation](https://www.youtube.com/watch?v=fU9hR3kiOK0) (40 mins).
See what happens when you re-imagine a database as a stream!! Look at
2016-12-03 10:44:06 +00:00
all the problems that evaporate.
2016-10-26 20:05:05 +00:00
Think about that: shared mutable state (the root of all evil),
re-imagined as a stream!! Blew my socks off.
2017-02-02 19:58:58 +00:00
If, by chance, you ever watched that video (you should!), you might then twig to
the idea that `app-db` is really a derived value ... the video talks
2016-12-03 10:44:06 +00:00
a lot about derived values. So, yes, app-db is a derived value of the `perpetual reduce`.
2016-10-26 20:05:05 +00:00
2017-02-02 19:58:58 +00:00
And yet, it acts as the authoritative source of state in the app. And yet,
2016-12-03 20:57:31 +00:00
it isn't, it is simply a piece of derived state. And yet, it is the source. Etc.
2016-10-26 20:05:05 +00:00
2016-12-03 20:57:31 +00:00
This is an infinite loop of sorts - an infinite loop of derived data.
2016-10-26 20:05:05 +00:00
2016-10-22 03:56:04 +00:00
## It does FSM
2016-10-21 05:08:16 +00:00
2017-02-02 19:58:58 +00:00
> Any sufficiently complicated GUI contains an ad hoc,
> informally-specified, bug-ridden, slow implementation
2016-10-25 06:22:49 +00:00
> of a hierarchical Finite State Machine <br>
2016-12-04 20:59:11 +00:00
> -- me, trying too hard to impress my two twitter followers
2016-10-21 05:08:16 +00:00
2016-10-26 20:05:05 +00:00
`event handlers` collectively
implement the "control" part of an application. Their logic
interprets arriving events in the context of existing state,
2017-07-16 09:49:40 +00:00
and they compute the new state of the application.
2016-10-21 05:08:16 +00:00
2016-12-17 06:28:26 +00:00
`events` act a bit like the `triggers` in a finite state machine, and
the `event handlers` act like the rules which govern how the state machine
2016-10-21 05:08:16 +00:00
moves from one logical state to the next.
2016-11-30 10:28:01 +00:00
In the simplest
case, `app-db` will contain a single value which represents the current "logical state".
2017-02-02 19:58:58 +00:00
For example, there might be a single `:phase` key which can have values like `:loading`,
`:not-authenticated` `:waiting`, etc. Or, the "logical state" could be a function
of many values in `app-db`.
2016-10-24 03:21:47 +00:00
2017-02-02 19:58:58 +00:00
Not every app has lots of logical states, but some do, and if you are implementing
one of them, then formally recognising it and using a technique like
2016-10-21 05:08:16 +00:00
[State Charts](https://www.amazon.com/Constructing-User-Interface-Statecharts-Horrocks/dp/0201342782)
2016-10-24 03:21:47 +00:00
will help greatly in getting a clean design and fewer bugs.
2017-02-02 19:58:58 +00:00
The beauty of re-frame, from a FSM point of view, is that all the state is
in one place - unlike OO systems where the state is distributed (and synchronised)
2016-10-24 03:21:47 +00:00
across many objects. So implementing your control logic as a FSM is
2017-02-02 19:58:58 +00:00
fairly natural in re-frame, whereas it is often difficult and
2016-11-30 10:28:01 +00:00
contrived in other kinds of architecture (in my experience).
2016-10-21 05:08:16 +00:00
2016-10-24 03:21:47 +00:00
So, members of the jury, I put it to you that:
2016-10-21 05:08:16 +00:00
- the first 3 dominoes implement an [Event-driven finite-state machine](https://en.wikipedia.org/wiki/Event-driven_finite-state_machine)
- the last 3 dominoes render of the FSM's current state for the user to observe
2016-10-24 03:21:47 +00:00
2016-10-25 10:46:56 +00:00
Depending on your app, this may or may not be a useful mental model,
2017-02-02 19:58:58 +00:00
but one thing is for sure ...
2016-10-24 03:21:47 +00:00
2016-10-21 05:08:16 +00:00
Events - that's the way we roll.
2016-12-19 01:54:18 +00:00
## Interconnections
2017-02-02 19:58:58 +00:00
Ask a Systems Theorist, and they'll tell you that a system has **parts** and **interconnections**.
2016-12-19 01:54:18 +00:00
Human brains tend to focus first on the **parts**, and then, later, maybe on
2017-02-02 19:58:58 +00:00
**interconnections**. But we know better, right? We
2016-12-19 01:54:18 +00:00
know interconnections are often critical to a system.
"Focus on the lines between the boxes" we lecture anyone kind enough to listen (in my case, glassy-eyed family members).
In the case of re-frame, dominoes are the **parts**, so, tick, yes, we have
looked at them first. Our brains are happy. But what about the **interconnections**?
2017-02-02 19:58:58 +00:00
If the **parts** are functions, as is the case with re-frame,
what does it even mean to talk about **interconnections between functions?**
2016-12-19 01:54:18 +00:00
To answer that question, I'll rephrase it as:
how are the domino functions **composed**?
2017-02-02 19:58:58 +00:00
At the language level,
2016-12-19 01:54:18 +00:00
Uncle Alonzo and Uncle John tell us how a function like `count` composes:
```clj
(str (count (filter odd? [1 2 3 4 5])))
```
2017-02-02 19:58:58 +00:00
We know when `count` is called, and with what
argument, and how the value it computes becomes the arg for a further function.
2016-12-19 01:54:18 +00:00
We know how data "flows" into and out of the functions.
2017-02-02 19:58:58 +00:00
Sometimes, we'd rewrite this code as:
2016-12-19 01:54:18 +00:00
```clj
(->> [1 2 3 4 5]
(filter odd?)
count
str)
```
2017-02-02 19:58:58 +00:00
With this arrangement, we talk of "threading" data
2017-07-16 00:08:03 +00:00
through functions. **It seems to help our comprehension to conceive function
2016-12-19 01:54:18 +00:00
composition in terms of data flow**.
2017-01-02 00:50:28 +00:00
re-frame delivers architecture
2017-02-02 19:58:58 +00:00
by supplying the interconnections - it threads the data - it composes the dominoes - it is the lines between the boxes.
2016-12-19 01:54:18 +00:00
2017-02-02 19:58:58 +00:00
But it doesn't have a universal method for this "composition". The technique it uses varies from one domino
neighbour-pair to the next. Initially, it uses a queue/router, then a pipeline of interceptors
2017-01-02 00:50:28 +00:00
and, finally, a Signal Graph.
2017-02-02 19:58:58 +00:00
Remember back in the original README? Our analogy for re-frame was the water cycle - water flowing around the loop,
2017-01-02 00:50:28 +00:00
compelled by different kinds of forces at different times (gravity, convection, etc), going through phase changes.
2017-02-02 19:58:58 +00:00
With this focus on interconnections, we have been looking on the "forces" part of the loop. The transport.
2016-12-04 20:59:11 +00:00
## Full Stack
If you like re-frame and want to take the principles full-stack, then
2017-01-02 02:00:05 +00:00
these resources might be interesting to you:
2016-12-04 20:59:11 +00:00
Commander Pattern
2017-02-02 19:58:58 +00:00
https://www.youtube.com/watch?v=B1-gS0oEtYc
2016-12-04 20:59:11 +00:00
Datalog All The Way Down
https://www.youtube.com/watch?v=aI0zVzzoK_E
2017-01-02 02:00:05 +00:00
Reactive PostgreSQL:
https://yogthos.net/posts/2016-11-05-LuminusPostgresNotifications.html
2016-12-04 20:59:11 +00:00
## What Of This Romance?
2016-12-03 10:44:06 +00:00
My job is to be a relentless cheerleader for re-frame, right?
The gyrations of my Pom-Poms should be tectonic,
2016-12-03 20:57:31 +00:00
but the following quote makes me smile. It should
2016-12-03 10:44:06 +00:00
be taught in all ComSci courses.
> We begin in admiration and end by organizing our disappointment <br>
> &nbsp;&nbsp;&nbsp; -- Gaston Bachelard (French philosopher)
2016-12-03 20:57:31 +00:00
Of course, that only applies if you get passionate about a technology
(a flaw of mine).
2016-12-03 10:44:06 +00:00
But, no. No! Those French Philosophers and their pessimism - ignore him!!
Your love for re-frame will be deep, abiding and enriching.
2017-02-02 19:58:58 +00:00
***
2016-12-04 11:26:01 +00:00
Previous: [The API](API.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
2016-12-04 11:26:01 +00:00
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
2016-12-20 12:02:13 +00:00
Next: [Infographic Overview](EventHandlingInfographic.md)
[SPAs]:http://en.wikipedia.org/wiki/Single-page_application
[SPA]:http://en.wikipedia.org/wiki/Single-page_application
[Reagent]:http://reagent-project.github.io/
[Dan Holmsand]:https://twitter.com/holmsand
[Flux]:http://facebook.github.io/flux/docs/overview.html#content
[Elm]:http://elm-lang.org/
[OM]:https://github.com/swannodette/om
[Hoplon]:http://hoplon.io/
[Pedestal App]:https://github.com/pedestal/pedestal-app
2017-07-16 00:30:57 +00:00
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
<!-- END doctoc generated TOC please keep comment here to allow auto update -->