Merge branch 'develop'

This commit is contained in:
Daniel Compton 2017-02-09 22:25:07 +13:00
commit 9a93a8eec4
7 changed files with 175 additions and 181 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
.idea/**/*
**/.idea/**/*
!.idea/codeStyleSettings.xml
*.iml
*.log

View File

@ -10,13 +10,13 @@
re-frame puts all your application state into one place, which is
called `app-db`.
Ideally, you will provide a spec for this data-in-the-one-place,
Ideally, you will provide a spec for this data-in-the-one-place,
[using a powerful and leverageable schema](http://clojure.org/about/spec).
Now, this advice is not the slightest bit controversial for 'real' databases, right?
You'd happily put all your well-formed data into PostgreSQL.
But within a running application (in memory), there can be hesitation. If you have
But within a running application (in memory), there can be hesitation. If you have
a background in OO, this data-in-one-place
business is a really, really hard one to swallow. You've
spent your life breaking systems into pieces, organised around behaviour and trying
@ -39,15 +39,15 @@ seems a more useful paradigm than plain old map-in-atom.
Further Notes:
1. `app-state` would probably be a more accurate name, but I choose `app-db` instead because
1. `app-state` would probably be a more accurate name, but I choose `app-db` instead because
I wanted to convey the in-memory database notion as strongly as possible.
2. In the documentation and code, I make a distinction between `app-db` (the `ratom`) and
2. In the documentation and code, I make a distinction between `app-db` (the `ratom`) and
`db` which is the (map) `value` currently stored **inside** this `ratom`. Be aware of that naming as you read code.
3. re-frame creates and manages an `app-db` for you, so
you don't need to declare one yourself (see the [the first FAQ](FAQs/Inspecting-app-db.md) if you want
you don't need to declare one yourself (see the [the first FAQ](FAQs/Inspecting-app-db.md) if you want
to inspect the value it holds).
4. `app-db` doesn't actually have to be a `ratom` containing a map. It could, for example,
be a [datascript database](https://github.com/tonsky/datascript). In fact, any database which
4. `app-db` doesn't actually have to be a `ratom` containing a map. It could, for example,
be a [datascript database](https://github.com/tonsky/datascript). In fact, any database which
can signal you when it changes would do. We'd love! to be using [datascript database](https://github.com/tonsky/datascript) - so damn cool -
but we had too much data in our apps. If you were to use it, you'd have to tweak re-frame a bit and use [Posh](https://github.com/mpdairy/posh).
@ -55,35 +55,35 @@ Further Notes:
### The Benefits Of Data-In-The-One-Place
1. Here's the big one: because there is a single source of truth, we write no
code to synchronize state between many different stateful components. I
code to synchronise state between many different stateful components. I
cannot stress enough how significant this is. You end up writing less code
and an entire class of bugs is eliminated.
(This mindset is very different to OO which involves
distributing state across objects, and then ensuring that state is synchronized, all the while
distributing state across objects, and then ensuring that state is synchronised, all the while
trying to hide it, which is, when you think about it, quite crazy ... and I did it for years).
2. Because all app state is coalesced into one atom, it can be updated
with a single `reset!`, which acts like a transactional commit. There is
an instant in which the app goes from one state to the next, never a series
of incremental steps which can leave the app in a temporarily inconsistent, intermediate state.
2. Because all app state is coalesced into one atom, it can be updated
with a single `reset!`, which acts like a transactional commit. There is
an instant in which the app goes from one state to the next, never a series
of incremental steps which can leave the app in a temporarily inconsistent, intermediate state.
Again, this simplicity causes a certain class of bugs or design problems to evaporate.
3. The data in `app-db` can be given a strong schema
so that, at any moment, we can validate all the data in the application. **All of it!**
We do this check after every single "event handler" runs (event handlers compute new state).
And this enables us to catch errors early (and accurately). It increases confidence in the way
so that, at any moment, we can validate all the data in the application. **All of it!**
We do this check after every single "event handler" runs (event handlers compute new state).
And this enables us to catch errors early (and accurately). It increases confidence in the way
that Types can increase confidence, only [a good schema can potentially provide more
**leverage** than types](https://www.youtube.com/watch?v=nqY4nUMfus8).
**leverage** than types](https://www.youtube.com/watch?v=nqY4nUMfus8).
4. Undo/Redo [becomes straight forward to implement](https://github.com/Day8/re-frame-undo).
It is easy to snapshot and restore one central value. Immutable data structures have a
4. Undo/Redo [becomes straight forward to implement](https://github.com/Day8/re-frame-undo).
It is easy to snapshot and restore one central value. Immutable data structures have a
feature called `structural sharing` which means it doesn't cost much RAM to keep the last, say, 200
snapshots. All very efficient.
For certain categories of applications (eg: drawing applications) this feature is borderline magic.
Instead of undo/redo being hard, disruptive and error prone, it becomes trivial.
**But,** many web applications are not self contained
data-wise and, instead, are dominated by data sourced from an authoritative, remote database.
For these applications, re-frame's `app-db` is mostly a local caching
snapshots. All very efficient.
For certain categories of applications (eg: drawing applications) this feature is borderline magic.
Instead of undo/redo being hard, disruptive and error prone, it becomes trivial.
**But,** many web applications are not self contained
data-wise and, instead, are dominated by data sourced from an authoritative, remote database.
For these applications, re-frame's `app-db` is mostly a local caching
point, and being able to do undo/redo its state is meaningless because the authoritative
source of data is elsewhere.
@ -97,15 +97,15 @@ source of data is elsewhere.
You need to create a [spec](http://clojure.org/about/spec) schema for `app-db`. You want that leverage.
Of course, that means you'll have to learn [spec](http://clojure.org/about/spec) and there's
some overhead in that, so maybe, just maybe, in your initial experiments, you can
some overhead in that, so maybe, just maybe, in your initial experiments, you can
get away without one. But not for long. Promise me you'll write a `spec`. Promise me. Okay, good.
Soon we'll look at the [todomvc example](https://github.com/Day8/re-frame/tree/master/examples/todomvc)
which shows how to use a spec. (Check out `src/db.cljs` for the spec itself, and then in `src/events.cljs` for
how to write code which checks `app-db` against this spec after every single event has been
how to write code which checks `app-db` against this spec after every single event has been
processed.)
Specs are potentially more leveragable than types. This is a big interesting idea which is not yet mainstream.
Specs are potentially more leveragable than types. This is a big interesting idea which is not yet mainstream.
Watch how: <br>
https://www.youtube.com/watch?v=VNTQ-M_uSo8
@ -116,7 +116,7 @@ https://vimeo.com/195711510
See [FAQ #1](FAQs/Inspecting-app-db.md)
***
***
Previous: [This Repo's README](../README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -66,7 +66,7 @@ Then:
So, what's just happened? The ClojureScript code under `src` has been compiled across to `javascript` and
put into `/resources/public/js/client.js` which is loaded into `/resources/public/example.html` (the HTML you just openned)
Figwheel provides for hot-loading, so you can edit the source and watch the loaded HTML change.
Figwheel provides for hot-loading, so you can edit the source and watch the loaded HTML change.
## Namespace
@ -110,7 +110,7 @@ format for events. For example:
[:delete-item 42]
```
The first element in the vector identifies the `kind` of `event`. The
The first element in the vector is a keyword which identifies the `kind` of `event`. The
further elements are optional, and can provide additional data
associated with the event. The additional value above, `42`, is
presumably the id of the item to delete.
@ -121,8 +121,7 @@ Here are some other example events:
[:dressing/put-pants-on "velour flares" {:method :left-leg-first :belt false}]
```
The `kind` of event is a keyword, and for non-trivial
applications it will be namespaced.
(For non-trivial applications, the `kind` keyword will be namespaced.)
**Rule**: events are pure data. No sneaky tricks like putting
callback functions on the wire. You know who you are.
@ -172,65 +171,62 @@ for each kind of event.
Collectively, event handlers provide the control logic in a re-frame application.
In this application, 3 kinds of event are dispatched:
`:initialise`
`:initialize`
`:time-color-change`
`:timer`
3 events means we'll be registering 3 event handlers.
3 events means we'll be registering 3 event handlers.
### Two ways To register
### Two ways to register
Event handlers can be registered via either `reg-event-db`
or `reg-event-fx` (`-db` vs `-fx`).
Handler functions take `coeffects` (input args) and return `effects`.
Handler functions take `coeffects` (input args) and return `effects`,
however `reg-event-db` allows you to write simpler handlers.
The handler functions it registers (1) take just one `coeffect` -
the current app state, and (2) return only one `effect` -
the updated app state.
Whereas `reg-event-fx` registered handlers are more flexible.
Handlers can be registered via either `reg-event-fx`
or `reg-event-db` (`-fx` vs `-db`):
Because of its simplicity, we'll be using the former here.
- `reg-event-fx` can take multiple `coeffects` and can return multiple `effects`, while
- `reg-event-db` allows you to write simpler handlers in the common case where you want
them to take only one `coeffect` - the current app state - and return one `effect` - the
updated app state.
Because of its simplicity, we'll be using the latter here.
### reg-event-db
We register event handlers using re-frame's `reg-event-db`.
We register event handlers using re-frame's `reg-event-db`:
`reg-event-db` is used like this:
```clj
(rf/reg-event-db
:the-event-id
the-event-handler-fn)
```
The handler function you provide should expect two parameters:
- `db` the current application state (contents of `app-db`)
- `v` the event vector
The handler function you provide should expect two arguments:
- `db`, the current application state (the value contained in `app-db`)
- `v`, the event vector
So, your function will have a signature like this: `(fn [db v] ...)`.
Each event handler must compute and return the new state of
the application, which means it normally returns a
modified version of `db`.
the application, which means it returns a
modified version of `db` (or an unmodified one, if there are to be no changes to the state).
### :initialize
On startup, application state must be initialised. We
want to put a sensible value into `app-db` which will
otherwise contain `{}`.
On startup, application state must be initialized. We
want to put a sensible value into `app-db`, which starts out containing `{}`.
So a `(dispatch [:initialize])` will happen early in the
apps life (more on this below), and we need to write an `event handler`
app's life (more on this below), and we need to write an `event handler`
for it.
Now this event handler is slightly unusual because it doesn't
much care about the existing value in `db` - it just wants to plonk
a completely new value.
Now this event handler is slightly unusual because not only does it not care about
any event information passed in via the `event` vector, but it doesn't
even care about the existing value in `db` - it just wants to plonk
a completely new value:
Like this:
```clj
(rf/reg-event-db ;; sets up initial application state
:initialize
:initialize
(fn [_ _] ;; the two parameters are not important here, so use _
{:time (js/Date.) ;; What it returns becomes the new application state
:time-color "#f88"})) ;; so the application state will initially be a map with two keys
@ -241,12 +237,12 @@ This particular handler `fn` ignores the two parameters
a map literal, which becomes the application
state.
Here's an alternative way of writing it which does pay attention to the existing value of `db`:
For comparison, here's how we would have written this if we'd cared about the existing value of `db`:
```clj
(rf/reg-event-db
:initialize
(fn [db _] ;; we use db this time, so name it
(-> db
:initialize
(fn [db _] ;; we use db this time, so name it
(-> db
(assoc :time (js/Date.))
(assoc :time-color "#f88")))
```
@ -259,7 +255,7 @@ Earlier, we set up a timer function to `(dispatch [:timer now])` every second.
Here's how we handle it:
```clj
(rf/reg-event-db ;; usage: (rf/dispatch [:timer a-js-Date])
:timer
:timer
(fn [db [_ new-time]] ;; <-- de-structure the event vector
(assoc db :time new-time))) ;; compute and return the new application state
```
@ -273,34 +269,34 @@ Notes:
When the user enters a new colour value (via an input text box):
```clj
(rf/reg-event-db
(rf/reg-event-db
:time-color-change ;; usage: (rf/dispatch [:time-color-change 34562])
(fn [db [_ new-color-value]]
(fn [db [_ new-color-value]]
(assoc db :time-color new-color-value))) ;; compute and return the new application state
```
## Effect Handlers (domino 3)
Domino 3 actions/realises the `effects` returned by event handlers.
Domino 3 realises/puts into action the `effects` returned by event handlers.
In this "simple" application, our event handlers are implicitly returning
only one effect: "update application state".
This particular `effect` is actioned by a re-frame supplied
`effect handler`. **So, there's nothing for us to do for this domino**. We are
This particular `effect` is accomplished by a re-frame-supplied
`effect` handler. **So, there's nothing for us to do for this domino**. We are
using a standard re-frame effect handler.
And this is not unusual. You'll seldom have to write `effect handlers`, but
we'll understand more about them in a later tutorial.
And this is not unusual. You'll seldom have to write `effect` handlers, but in a later
tutorial we'll show you more about how to do so when you need to.
## Subscription Handlers (domino 4)
Subscription handlers take application state as an argument,
and they compute a query over it, returning something of
Subscription handlers, or `query` functions, take application state as an argument
and run a query over it, returning something called
a "materialised view" of that application state.
When the application state changes, subscription functions are
re-run by re-frame, to compute new values (a new materialised view).
re-run by re-frame, to compute new values (new materialised views).
Ultimately, the data returned by `query` functions is used
in the `view` functions (Domino 5).
@ -309,13 +305,13 @@ One subscription can
source data from other subscriptions. So it is possible to
create a tree of dependencies.
The Views (Domino 5) are the leaves of this tree The tree's
The Views (Domino 5) are the leaves of this tree. The tree's
root is `app-db` and the intermediate nodes between the two
are computations being performed by the query functions of Domino 4.
Now, the two examples below are trivial. They just extract part of the application
state and return it. So, there's virtually no computation. A more interesting tree
of subscriptions and more explanation can be found in the todomvc example.
of subscriptions, and more explanation, can be found in the todomvc example.
### reg-sub
@ -326,15 +322,15 @@ of subscriptions and more explanation can be found in the todomvc example.
:some-query-id ;; query id (used later in subscribe)
a-query-fn) ;; the function which will compute the query
```
If, later, a view function subscribes to a query like this:
`(subscribe [:some-query-id])`. Note use of `:some-query-id`
then `a-query-fn` will be used to perform the query over the application state.
Then later, a view function subscribes to a query like this:
`(subscribe [:some-query-id])`, and `a-query-fn` will be used
to perform the query over the application state.
Each time application state changes, `a-query-fn` will be
called again to compute a new materialised view (a new computation over app state)
and that new value will be given to any view function which is subscribed
to `:some-query-id`. This view function, itself, will then also be called again
to compute new DOM (because it depends on a query value which changed).
and that new value will be given to all `view` functions which are subscribed
to `:some-query-id`. These `view` functions will then be called to compute the
new DOM state (because the views depend on query results which have changed).
Along this reactive chain of dependencies, re-frame will ensure the
necessary calls are made, at the right time.
@ -358,7 +354,7 @@ Like I said, both of these queries are trivial. See [todomvc.subs.clj](https://g
`view` functions turn data into DOM. They are "State in, Hiccup out" and they are Reagent components.
Any SPA will have lots of `view`functions, and collectively,
Any SPA will have lots of `view` functions, and collectively,
they render the app's entire UI.
### Hiccup
@ -366,7 +362,7 @@ they render the app's entire UI.
`Hiccup` is a data format for representing HTML.
Here's a trivial view function which returns hiccup-formatted data:
```clj
```clj
(defn greet
[]
[:div "Hello viewers"]) ;; means <div>Hello viewers</div>
@ -383,11 +379,11 @@ And if we call it:
Yep, that's a vector with two elements: a keyword and a string.
Now,`greet` is pretty simple because it only has the "Hiccup Out" part. There's no "Data In".
Now, `greet` is pretty simple because it only has the "Hiccup Out" part. There's no "Data In".
### Subscribing
To render the DOM representation of some-part-of app state, view functions must query
To render the DOM representation of some part of the app state, view functions must query
for that part of `app-db`, and that means using `subscribe`.
`subscribe` is always called like this:
@ -396,7 +392,7 @@ for that part of `app-db`, and that means using `subscribe`.
```
There's only one (global) `subscribe` function and it takes one argument, assumed to be a vector.
The first element in the vector (shown above as `query-id`) identifies/names the query
The first element in the vector (shown above as `query-id`) identifies the query,
and the other elements are optional
query parameters. With a traditional database a query might be:
```
@ -404,7 +400,7 @@ select * from customers where name="blah"
```
In re-frame, that would be done as follows:
`(subscribe [:customer-query "blah"])`
`(subscribe [:customer-query "blah"])`,
which would return a `ratom` holding the customer state (a value which might change over time!).
> Because subscriptions return a ratom, they must always be dereferenced to
@ -439,10 +435,9 @@ And this view function renders the input field:
Notice how it does BOTH a `subscribe` to obtain the current value AND a `dispatch` to say when it has changed.
It is very common for view functions to render event-dispatching functions. The user's interaction with
the UI is usually the largest source of events.
It is very common for view functions to run event-dispatching functions. The user's interaction with the UI is usually the largest source of events.
And then something more standard:
And then a `view` function to bring the others together, which contains no subscriptions or dispatching of its own:
```clj
(defn ui
[]
@ -467,17 +462,17 @@ Django, Rails, Handlebars or Mustache -- they map data to HTML -- except for two
downside is that these are not "designer friendly" HTML templates.
2. these templates are reactive. When their input Signals change, they
are automatically rerun, producing new DOM. Reagent adroitly shields you from the details, but
the renderer of any `component` is wrapped by a `reaction`. If any of the the "inputs"
to that render change, the render is rerun.
the renderer of any `component` is wrapped by a `reaction`. If any of the "inputs"
to that renderer change, the renderer is rerun.
## Kick Starting The App
Below, `run` is the called when the HTML page has loaded
to kick off the application.
Below, `run` is the called to kick off the application once the HTML page has loaded.
It has two tasks:
1. load the initial application state
2. "mount" the GUI onto an existing DOM element.
1. Load the initial application state
2. Load the GUI by "mounting" the root-level function in the hierarchy of `view` functions -- in our case, `ui` --
onto an existing DOM element.
```clj
(defn ^:export run
@ -487,19 +482,18 @@ It has two tasks:
(js/document.getElementById "app")))
```
After `run` is called, the app passively waits for events.
After `run` is called, the app passively waits for `events`.
Nothing happens without an `event`.
When it comes to establishing initial application state, you'll
notice the use of `dispatch-sync`, rather than `dispatch`. This is something of
cheat which ensures a correct
notice the use of `dispatch-sync`, rather than `dispatch`. This is a cheat which ensures that a correct
structure exists in `app-db` before any subscriptions or event handlers run.
## Summary
**Your job**, when building an app, is to:
- design your app's information model (data and schema layer)
- write and register event handler functions (control and transition layer) (domino 2)
- write and register event handler functions (control and transition layer) (domino 2)
- (once in a blue moon) write and register effect and coeffect handler
functions (domino 3) which do the mutative dirty work of which we dare not
speak in a pure, immutable functional context. Most of the time, you'll be
@ -509,7 +503,7 @@ structure exists in `app-db` before any subscriptions or event handlers run.
## Next Steps
You should now take time to carefully review the [todomvc example application](https://github.com/Day8/re-frame/tree/develop/examples/todomvc).
You should now take time to carefully review the [todomvc example application](https://github.com/Day8/re-frame/tree/develop/examples/todomvc).
After that, you'll be ready to write your own code. Perhaps you will use a
template to create your own project: <br>

View File

@ -65,7 +65,7 @@ with a function to do the handling:
```clj
(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
(fn [db [_ new-value]] ;; this function does the handling
(assoc db :flag new-value)))
```

View File

@ -17,7 +17,7 @@ But **there are other perspectives** on re-frame
which will deepen your understanding.
This tutorial is a tour of these ideas, justifications and insights.
It is a little rambling, but I'm hoping it will deliver for you
It is a little rambling, but I'm hoping it will deliver for you
at least one "Aaaah, I see" moment before the end.
> If a factory is torn down but the rationality which produced it is
@ -53,10 +53,10 @@ For all its considerable brilliance, Reagent (+ React)
delivers only the 'V' part of a traditional MVC framework.
But apps involve much more than V. We build quite complicated
SPAs which can run to 50K lines of code. So, I wanted to know:
SPAs which can run to 50K lines of code. So, I wanted to know:
where does the control logic go? How is state stored & manipulated? etc.
We read up on [Pedestal App], [Flux],
We read up on [Pedestal App], [Flux],
[Hoplon], [Om], early [Elm], etc., and re-frame is the architecture that
emerged. Since then, we've tried to keep an eye on further developments like the
Elm Architecture, Om.Next, BEST, Cycle.js, Redux, etc. They have taught us much
@ -86,18 +86,18 @@ a system out of pure functions.
__Third__, we believe in the primacy of data, for the reasons described in
the main README. re-frame has a data oriented, functional architecture.
__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.
__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.
It doesn't take over everything in re-frame - it just does part of the job.
__Finally__, a long time ago in a galaxy far far away, I was lucky enough to program in Eiffel
__Finally__, a long time ago in a galaxy far far away, I was lucky enough to program in Eiffel
where I was exposed to the idea of [command-query separation](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation).
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>
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>
Sincerely, The Self-appointed President of the Cursor Skeptic's Society.
## It does Event Sourcing
@ -119,7 +119,7 @@ Note: that's all just data. **Pure, lovely loggable data.**
If you have that data, then you can reproduce the error.
re-frame allows you to time travel, even in a production setting.
re-frame allows you to time travel, even in a production setting.
Install the "checkpoint" state into `app-db`
and then "play forward" through the collection of dispatched events.
@ -131,22 +131,22 @@ a checkpoint of `app-db`, and the events since then.
Here's Martin Fowler's [description of Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html).
## It does a reduce
## It does a reduce
Here's an interesting way of thinking about the re-frame
Here's an interesting way of thinking about the re-frame
data flow ...
**First**, imagine that all the events ever dispatched in a
**First**, imagine that all the events ever dispatched in a
certain running app were stored in a collection (yes, event sourcing again).
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
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
the collection, and so on and so on. We'd end up with a
collection of event vectors.
**Second**, remind yourself that the `combining function`
**Second**, remind yourself that the `combining function`
of a `reduce` takes two arguments:
1. the current state of the reduction and
2. the next collection member to fold in
@ -157,48 +157,48 @@ Then notice that `reg-event-db` event handlers take two arguments also:
Interesting. That's the same as a `combining function` in a `reduce`!!
So now we can introduce the new mental model: at any point in time,
So now we can introduce the new mental model: at any point in 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
the entire `collection` of events dispatched in the app up until
that time. The combining function for this reduce is the set of event handlers.
It is almost like `app-db` is the temporary place where this
It is almost like `app-db` is the temporary place where this
imagined `perpetual reduce` stores its on-going reduction.
Now, in the general case, this perspective breaks down a bit,
because of `reg-event-fx` (has `-fx` on the end, not `-db`) which
Now, in the general case, this perspective breaks down a bit,
because of `reg-event-fx` (has `-fx` on the end, not `-db`) which
allows:
1. Event handlers to produce `effects` beyond just application state
1. Event handlers to produce `effects` beyond just application state
changes.
2. Event handlers to have `coeffects` (arguments) in addition to `db` and `v`.
2. Event handlers to have `coeffects` (arguments) in addition to `db` and `v`.
But, even if it isn't the full picture, it is a very useful
But, even if it isn't the full picture, it is a very useful
and interesting mental model. We were first exposed to this idea
via Elm's early use of `foldp` (fold from the past), which was later enshrined in the
via Elm's early use of `foldp` (fold from the past), which was later enshrined in the
Elm Architecture.
## Derived Data All The Way Down
For the love of all that is good, please watch this terrific
[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
[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
all the problems that evaporate.
Think about that: shared mutable state (the root of all evil),
re-imagined as a stream!! Blew my socks off.
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
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
a lot about derived values. So, yes, app-db is a derived value of the `perpetual reduce`.
And yet, it acts as the authoritative source of state in the app. And yet,
And yet, it acts as the authoritative source of state in the app. And yet,
it isn't, it is simply a piece of derived state. And yet, it is the source. Etc.
This is an infinite loop of sorts - an infinite loop of derived data.
## It does FSM
> Any sufficiently complicated GUI contains an ad hoc,
> informally-specified, bug-ridden, slow implementation
> Any sufficiently complicated GUI contains an ad hoc,
> informally-specified, bug-ridden, slow implementation
> of a hierarchical Finite State Machine <br>
> -- me, trying too hard to impress my two twitter followers
@ -213,19 +213,19 @@ moves from one logical state to the next.
In the simplest
case, `app-db` will contain a single value which represents the current "logical state".
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`.
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`.
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
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
[State Charts](https://www.amazon.com/Constructing-User-Interface-Statecharts-Horrocks/dp/0201342782)
will help greatly in getting a clean design and fewer bugs.
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 synchronized)
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)
across many objects. So implementing your control logic as a FSM is
fairly natural in re-frame, whereas it is often difficult and
fairly natural in re-frame, whereas it is often difficult and
contrived in other kinds of architecture (in my experience).
So, members of the jury, I put it to you that:
@ -233,59 +233,59 @@ So, members of the jury, I put it to you that:
- the last 3 dominoes render of the FSM's current state for the user to observe
Depending on your app, this may or may not be a useful mental model,
but one thing is for sure ...
but one thing is for sure ...
Events - that's the way we roll.
## Interconnections
Ask a Systems Theorist, and they'll tell you that a system has **parts** and **interconnections**.
Ask a Systems Theorist, and they'll tell you that a system has **parts** and **interconnections**.
Human brains tend to focus first on the **parts**, and then, later, maybe on
**interconnections**. But we know better, right? We
**interconnections**. But we know better, right? We
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**?
If the **parts** are functions, as is the case with re-frame,
what does it even mean to talk about **interconnections between functions?**
If the **parts** are functions, as is the case with re-frame,
what does it even mean to talk about **interconnections between functions?**
To answer that question, I'll rephrase it as:
how are the domino functions **composed**?
At the language level,
At the language level,
Uncle Alonzo and Uncle John tell us how a function like `count` composes:
```clj
(str (count (filter odd? [1 2 3 4 5])))
```
We know when `count` is called, and with what
argument, and how the value it computes becomes the arg for a further function.
We know when `count` is called, and with what
argument, and how the value it computes becomes the arg for a further function.
We know how data "flows" into and out of the functions.
Sometimes, we'd rewrite this code as:
Sometimes, we'd rewrite this code as:
```clj
(->> [1 2 3 4 5]
(filter odd?)
count
str)
```
With this arrangement, we talk of "threading" data
through functions. **It seems to help our comprehension to frame function
With this arrangement, we talk of "threading" data
through functions. **It seems to help our comprehension to frame function
composition in terms of data flow**.
re-frame delivers architecture
by supplying the interconnections - it threads the data - it composes the dominoes - it is the lines between the boxes.
by supplying the interconnections - it threads the data - it composes the dominoes - it is the lines between the boxes.
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
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
and, finally, a Signal Graph.
Remember back in the original README? Our analogy for re-frame was the water cycle - water flowing around the loop,
Remember back in the original README? Our analogy for re-frame was the water cycle - water flowing around the loop,
compelled by different kinds of forces at different times (gravity, convection, etc), going through phase changes.
With this focus on interconnections, we have been looking on the "forces" part of the loop. The transport.
With this focus on interconnections, we have been looking on the "forces" part of the loop. The transport.
## Full Stack
@ -294,7 +294,7 @@ If you like re-frame and want to take the principles full-stack, then
these resources might be interesting to you:
Commander Pattern
https://www.youtube.com/watch?v=B1-gS0oEtYc
https://www.youtube.com/watch?v=B1-gS0oEtYc
Datalog All The Way Down
https://www.youtube.com/watch?v=aI0zVzzoK_E
@ -318,7 +318,7 @@ Of course, that only applies if you get passionate about a technology
But, no. No! Those French Philosophers and their pessimism - ignore him!!
Your love for re-frame will be deep, abiding and enriching.
***
***
Previous: [First Code Walk-Through](CodeWalkthrough.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -5,23 +5,23 @@ This tiny application is meant to provide a quick start of the basics of re-fram
A detailed source code walk-through is provided in the docs:
https://github.com/Day8/re-frame/blob/master/docs/CodeWalkthrough.md
All the code is in one namespace `/src/simpleexample/core.cljs`
All the code is in one namespace: `/src/simpleexample/core.cljs`
### Run It And Change It
Steps:
A. Check out the re-frame repo
1. Get a command line
2. `cd` to the root of this sub project (where this README exists)
3. run "`lein do clean, figwheel`" to compile the app,
4. open `http://localhost:3449/example.html` to see the app
1. Check out the re-frame repo
2. Get a command line
3. `cd` to the root of this sub project (where this README exists)
4. run "`lein do clean, figwheel`" to compile the app and start up figwheel hot-reloading,
5. open `http://localhost:3449/example.html` to see the app
Whileever step 3 is running, any changes you make to the ClojureScript
While step 4 is running, any changes you make to the ClojureScript
source files (in `src`) will be re-compiled and reflected in the running
page immediately.
### Production Version
Run "`lein do clean, with-profile prod compile`" to compile an optimized
version, and then open `resources/public/example.html`.
Run "`lein do clean, with-profile prod compile`" to compile an optimised
version, and then open `resources/public/example.html` in a browser.

View File

@ -29,7 +29,7 @@ A [re-frame](https://github.com/Day8/re-frame) implementation of [TodoMVC](http:
```
## Compile an optimized version
## Compile an optimised version
1. Compile
```