mirror of
https://github.com/status-im/re-frame.git
synced 2025-02-23 15:28:09 +00:00
Readme WIP
This commit is contained in:
parent
e383f3c1af
commit
57b1fbc160
150
README.md
150
README.md
@ -22,15 +22,16 @@ Todo:
|
||||
alt="Leiningen logo" title="The man himself" align="right" />
|
||||
-->
|
||||
|
||||
## Why Should You Care?
|
||||
## Why Should You Care About re-frame?
|
||||
|
||||
Either:
|
||||
|
||||
1. You need to develop an app in ClojureScript, and you are looking for a framework.
|
||||
2. You don't program in ClojureScript, but you have heard something about how "reactive programming"
|
||||
combined with "functional programming"
|
||||
principals and "immutable data" **is going to change everything**,
|
||||
and you are curious to get some insight into the claim.
|
||||
1. You want to develop an app in ClojureScript, and you are looking for a framework; or
|
||||
2. You believe that ReactJS decisively won the javascript framework wars in early 2015 and
|
||||
you are wondering how the combination of
|
||||
"reactive programming", "functional programming" and "immutable data" could
|
||||
**change everything**. Could this be a case of:
|
||||
"The future is already here - it's just not very evenly distributed".
|
||||
|
||||
|
||||
## re-frame
|
||||
@ -47,42 +48,45 @@ To build a re-frame app, you:
|
||||
|
||||
Features:
|
||||
|
||||
1. Nearly all the functions you write are pure, so the computational pieces of your app can
|
||||
be described, understood and tested independently.
|
||||
2. Composition of these computational parts happens via reactive data flows - a dynamic,
|
||||
unidirectional Signal graph.
|
||||
3. The resulting architecture involves "derived data" flowing in a two-stage, reactive loop (cycle),
|
||||
through pure functions. To put it another way, time is explicitly modeled.
|
||||
4. The jarring thing about re-frame is how simple it all is. Beautifully simple! Our reference
|
||||
implementation is little more than 100 lines of code.
|
||||
5. But it scales up so nicely to more complex apps. Frameworks are just pesky overhead at small
|
||||
scale - its how they help you to tame complexity that defines them.
|
||||
1. The functions you write are pure, so the computational pieces of your app can
|
||||
be described, understood and tested independently.
|
||||
You just won't need a sophisticated Dependency Injection system to test. So much
|
||||
incidental complexity evaporates.
|
||||
2. These computational parts are composed via reactive data flows - a dynamic,
|
||||
unidirectional Signal graph.
|
||||
3. The resulting architecture involves "derived data" flowing in a two-stage, reactive loop.
|
||||
Without realising it, you will be explicitly modeling time.
|
||||
4. It is fast, straight out of the box. You won't have to go thought [this sort of pain](http://blog.scalyr.com/2013/10/angularjs-1200ms-to-35ms/).
|
||||
5. The surprising thing about re-frame is how simple it is. Beautifully simple! Our reference
|
||||
implementation is little more than 100 lines of (clojurescript) code. Learn it in an afternoon.
|
||||
6. But it scales up so nicely to more complex apps. Frameworks are just pesky overhead at small
|
||||
scale - its how they help you to tame complexity that defines them.
|
||||
7. Re-frame is impressively buzzword compliant: it has FRP-nature,
|
||||
unidirectional data flow, pristinely pure functions, conveyor belts, statechart-friendliness (FSM)
|
||||
and claims an immaculate hammock conception.
|
||||
It also has a charming xkcd reference (soon)
|
||||
and a hilarious, insiders-joke T-shirt, ideal for conferences (in design).
|
||||
unidirectional data flow, pristinely pure functions, conveyor belts, statechart-friendliness (FSM)
|
||||
and claims an immaculate hammock conception.
|
||||
It also has a charming xkcd reference (soon)
|
||||
and a hilarious, insiders-joke T-shirt, ideal for conferences (in design).
|
||||
|
||||
**WARNING**: this document is long. That was the summary.
|
||||
__Warning__: this is a long document. That was the summary.
|
||||
|
||||
## What Problem Does It Solve?
|
||||
|
||||
We first decided to build apps with ClojureScript, then we choose [Reagent], then we had a problem.
|
||||
First we decided to build apps with ClojureScript, then we choose [Reagent], then we had a problem.
|
||||
|
||||
For all its considerable brilliance, Reagent (+ React)
|
||||
only delivers the 'V' part of a traditional MVC framework.
|
||||
For all its considerable brilliance, Reagent (+ ReactJS)
|
||||
delivers only the 'V' part of a traditional MVC framework.
|
||||
|
||||
But apps involve a lot more than V. Where
|
||||
But apps involve much more than V. Where
|
||||
does the control logic go? How is state stored and manipulated (remembering that shared, mutable state
|
||||
is the root of all evil). And how does the V bit source data?
|
||||
is the root of all evil). How does the V bit source data? Etc.
|
||||
|
||||
We wondered: what should the rest of the architecture look like? So we read up on [Flux], [Pedestal App],
|
||||
We wondered: what should the rest of the architecture look like? So we read about [Flux], [Pedestal App],
|
||||
[Hoplon], [OM], [Elm], etc and, slowly but surely, re-frame emerged.
|
||||
|
||||
|
||||
## Correct Acronym?
|
||||
|
||||
Is re-frame MVC? Or MVVM, or MVP? Or any of those?
|
||||
Is re-frame MVC or MVP or M*P? Any of those?
|
||||
|
||||
Hmm, I'd say "no".
|
||||
|
||||
@ -90,30 +94,36 @@ Your brow furrows. "But, Mike", you protest, "I've read the document once alrea
|
||||
and there's clearly a 'V' bit and there's a layer which is
|
||||
'C' related, and definitely an 'M'. How can it not be MVC?"
|
||||
|
||||
Look, I have sympathy for your position. But if McCoy were here he'd say "It's MVC, Jim,
|
||||
but not as we know it". In re-frame none of the M, V, or C bits are objects, they
|
||||
Yes, that's true. But to quote McCoy: "It's MVC, Jim,
|
||||
but not as we know it".
|
||||
|
||||
In re-frame none of the M, V, or C bits are objects, they
|
||||
are pure functions (or pure data), and
|
||||
and they are all wired together via reactive data flows. It is just such a different
|
||||
paradigm from traditional (smalltalk) MVC. Calling it MVC would just be confusing. I'd
|
||||
and they are all wired together via reactive data flows. It is just such a sufficiently different
|
||||
version of (traditional, smalltalk) MVC that calling it MVC would likely just be confusing. I'd
|
||||
love an alternative.
|
||||
|
||||
Perhaps it is a RACES framework - Reactive-Atom Component Event
|
||||
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.
|
||||
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
|
||||
*TODO:* get acronym down to 3 chars! Get an image of stacked Turtles for `DDATWD`
|
||||
insider's joke, conference T-Shirt.
|
||||
|
||||
|
||||
## A Mashup
|
||||
## Mostly A Mashup
|
||||
|
||||
Not much about re-frame is original or clever. You'll find
|
||||
no ingenious use of functional zippers, transducers or `core.async`.
|
||||
no ingenious use of functional zippers, transducers or core.async.
|
||||
|
||||
It does involve using Reagent's features in a slightly novel and advanced way. But
|
||||
apart from that minor point, which is really a credit to Reagent, not me,
|
||||
re-frame is a mashup of emerging ideas.
|
||||
Re-frame does use Reagent's features in a novel way.
|
||||
And we did actively reject
|
||||
the current ClojureScript fashion of using Cursors (which turned
|
||||
out to be a terrific decision).
|
||||
|
||||
But apart from that, for the most part, re-frame is a mashup of
|
||||
emerging ideas.
|
||||
|
||||
(For the record,
|
||||
one day I'd love to develop something original and clever).
|
||||
@ -140,34 +150,32 @@ squint a little to see the benefits that accrue at larger scale.
|
||||
|
||||
## Guiding Philosophy
|
||||
|
||||
First, above all we believe in the one true [Dan Holmsand], creator of Reagent, and
|
||||
__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.
|
||||
|
||||
Second, we believe in ClojureScript, immutable data and the process of building
|
||||
__Second__, we believe in ClojureScript, immutable data and the process of building
|
||||
a system out of pure functions.
|
||||
|
||||
Third, we believe that [FRP] is one honking great idea. You might be tempted to see
|
||||
__Third__, we believe that [FRP] is one honking great idea. You might be tempted to see
|
||||
Reagent as simply another of the React wrappers - a sibling to [OM] and
|
||||
[quiescent](https://github.com/levand/quiescent).
|
||||
But you'll only really "get"
|
||||
Reagent when you view it as an FRP-ish library. To put that another way, we think
|
||||
that Reagent, at its best, is closer in nature to [Hoplon] or [Elm] than it is OM.
|
||||
|
||||
Finally, we believe in one-way data flow. No two way data binding. We don't like read/write `cursors` which
|
||||
__Finally__, we believe in one-way data flow. No two way data binding. We don't like read/write `cursors` which
|
||||
promote the two way flow of data. As programs get bigger, we've found that their use seems to
|
||||
encourage control logic into all the wrong places.
|
||||
|
||||
re-frame does implement two way data flow (a loop of flow!), but it
|
||||
uses two, separate, one-way flows to achieve it, and those two flows
|
||||
are different in nature.
|
||||
|
||||
## FRP Clarifications
|
||||
|
||||
Terminology in the FRP world seems to get people hot under the collar. Those who believe in continuous-time
|
||||
semantics would object to me describing re-frame as having FRP-nature. They'd claim that it does something
|
||||
semantics might object to me describing re-frame as having FRP-nature. They'd claim that it does something
|
||||
different from pure FRP, which is true.
|
||||
|
||||
But, these days, FRP seems to have become a ["big tent" (a broad church?)](http://soft.vub.ac.be/Publications/2012/vub-soft-tr-12-13.pdf).
|
||||
But, these days, FRP seems to have become a
|
||||
["big tent"](http://soft.vub.ac.be/Publications/2012/vub-soft-tr-12-13.pdf)
|
||||
(a broad church?).
|
||||
Broad enough perhaps that re-frame can be in the far, top, left paddock of the tent, via a series of
|
||||
qualifications: re-frame has "discrete, dynamic, asynchronous, push FRP-ish-nature" without "glitch free" guarantees.
|
||||
(Surprisingly, "glitch" has specific meaning in FRP).
|
||||
@ -179,17 +187,19 @@ going further (certainly read the first two):
|
||||
- [presentation (video)](http://www.infoq.com/presentations/ClojureScript-Javelin) by Alan Dipert (co-author of Hoplon)
|
||||
- [serious pants Elm thesis](https://www.seas.harvard.edu/sites/default/files/files/archived/Czaplicki.pdf)
|
||||
|
||||
And for the love of all that is good, please watch this wonderful
|
||||
And for the love of all that is good, please watch this terrific
|
||||
[StrangeLoop presentation ](https://www.youtube.com/watch?v=fU9hR3kiOK0) (40 mins). Watch what happens
|
||||
when you re-imagine a database as a stream!! Look at all the problems that are solved. Think about that: shared mutable state (the root of all evil),
|
||||
when you re-imagine a database as a stream!! Look at all the problems that are solved.
|
||||
Think about that: shared mutable state (the root of all evil),
|
||||
re-imagined as a stream!! Blew my socks off.
|
||||
|
||||
re-frame tries to be `Derived Data everywhere, flowing`. Or perhaps,
|
||||
"Derived Data All The Way Down" (an infinite loop of Derived Data). More explanation on all these claims soon.
|
||||
`Derived Data All The Way Down` (an infinite loop of Derived Data).
|
||||
More explanation on all these claims soon.
|
||||
|
||||
## Explaining re-frame
|
||||
|
||||
To explain re-frame, I'll incrementally develop a diagram, explaining each part as it is added.
|
||||
To explain re-frame, I'll incrementally develop a diagram, describing each part as it is added.
|
||||
|
||||
Initially, I'll be using [Reagent] at an intermediate to advanced level. But this is no introductory
|
||||
reagent tutorial and you will need to have done one of those before continuing here. Try
|
||||
@ -241,8 +251,8 @@ A clarification: `app-db` doesn't actually have to be a reagent/atom containing
|
||||
a map. It could, for example, be a [datascript] database. In fact, any database which is reactive
|
||||
(can tell you when it changes) would do. (We'd love! to be using [datascript] - so damn cool -
|
||||
but we had too much
|
||||
data in our apps. If you were to use it, you'd have to
|
||||
[tweak the reference implementation a bit](https://gist.github.com/allgress/11348685)).
|
||||
data in our apps. If you were to use it, you'd have to tweak the reference implementation a bit
|
||||
[using this inspiration](https://gist.github.com/allgress/11348685)).
|
||||
|
||||
|
||||
### Benefits Arising From Data-In-The-One-Place
|
||||
@ -266,8 +276,8 @@ this is also very easy. Saving past states is trivial, and you will automaticall
|
||||
good sharing guarantees to keep the size of the snapshots down.
|
||||
|
||||
|
||||
To this list of benefits I would add two: the ability to genuinely model control via FSMs
|
||||
and the ability to do time travel debugging, even in production systems. More on both soon.
|
||||
To this list of benefits, I would briefly add two: the ability to genuinely model control via FSMs
|
||||
and the ability to do time travel debugging, even in a production setting. More on both soon.
|
||||
|
||||
[Hoplon] takes the same approach via what they called `stem cells`, which is a root source of data.
|
||||
|
||||
@ -295,7 +305,7 @@ Richard Dawkins
|
||||
|
||||
### How Flow Happens In Reagent
|
||||
|
||||
To implement FRP-ish-ness, Reagent provides a `ratom` and a `reaction`. re-frame uses both of these
|
||||
To implement FRP, Reagent provides a `ratom` and a `reaction`. re-frame uses both of these
|
||||
building blocks, so let's now make sure we understand them before going further.
|
||||
|
||||
`ratoms` behave just like normal ClojureScript atoms. You can `swap!` and `reset!` them, `watch` them, etc.
|
||||
@ -325,9 +335,9 @@ So, the `ratom` returned by a `reaction` is itself a Signal. Its value will chan
|
||||
the `computation` is re-run.
|
||||
|
||||
So, via the interplay between `ratoms` and `reactions`, values 'flow' into computations and out
|
||||
again, and then into other computations, etc. "Values" flow (propagate) through the Signal graph.
|
||||
again, and then into further computations, etc. "Values" flow (propagate) through the Signal graph.
|
||||
|
||||
But this Signal graph will be one without cycles, because cycles cause mayhem! re-frame achieves
|
||||
But this Signal graph must be without cycles, because cycles cause mayhem! re-frame achieves
|
||||
a unidirectional flow.
|
||||
|
||||
While the mechanics are different, `reaction` has the intent of `lift` in [Elm] and `defc=` in [Hoplon].
|
||||
@ -363,7 +373,8 @@ Right, so that was a lot of words. Some code to clarify:
|
||||
(println @ratom3) ;; ==> "World" ;; ratom3 is automatically updated too.
|
||||
```
|
||||
|
||||
So, in FRP-ish terms, a `reaction` will produce a "stream" of values over time (it is a Signal), accessible via the `ratom` it returns.
|
||||
So, in FRP-ish terms, a `reaction` will produce a "stream" of values over time (it is a Signal),
|
||||
accessible via the `ratom` it returns.
|
||||
|
||||
|
||||
Okay, that was all important background information for what is to follow. Back to the diagram ...
|
||||
@ -481,14 +492,16 @@ On with the rest of my lies and distortions...
|
||||
|
||||
### Components Like Templates?
|
||||
|
||||
A `component` like `greet` is like the templates you'd find in
|
||||
A `component` such as `greet` is like the templates you'd find in
|
||||
Django, Rails, Handlebars or Mustache -- it maps data to HTML -- except for two massive differences:
|
||||
- you have the full power of ClojureScript available to you (generating a Clojure data structure). The
|
||||
|
||||
1. you have the full power of ClojureScript available to you (generating a Clojure data structure). The
|
||||
downside is that these are not "designer friendly" HTML templates.
|
||||
- these templates are reactive. When their input Signals change, they
|
||||
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.
|
||||
to that render change, the render is rerun. In this sense, components are more like
|
||||
[Meteor views](https://www.meteor.com/try/2).
|
||||
|
||||
### React etc.
|
||||
|
||||
@ -496,7 +509,7 @@ Okay, so we have some unidirectional, dynamic, async, discrete FRP-ish data flow
|
||||
|
||||
Question: To which ocean does this river of data flow? Answer: The DOM ocean.
|
||||
|
||||
Let's see the full picture:
|
||||
The full picture:
|
||||
```
|
||||
app-db --> components --> Hiccup --> Reagent --> VDOM --> React --> DOM
|
||||
```
|
||||
@ -530,8 +543,9 @@ undergoes successive transformations, until pixels colour the monitor you to see
|
||||
|
||||
Derived Data, flowing. Every step is acting like a pure function and turning data into new data.
|
||||
|
||||
But, we don't have to bother ourselves with most of the pipeline. We just write the `components`
|
||||
part and Reagent/React will look after the rest. Back to the small picture ...
|
||||
All well and good, and nice to know, but we don't have to bother ourselves with most of the pipeline.
|
||||
We just write the `components`
|
||||
part and Reagent/React will look after the rest. So back we go to that part of the picture ...
|
||||
|
||||
|
||||
## Subscribe
|
||||
|
Loading…
x
Reference in New Issue
Block a user