From b6c301b31c7c9e9af4bf1ed3af8b4dbce70568da Mon Sep 17 00:00:00 2001 From: Mike Thompson Date: Sat, 15 Jul 2017 14:35:28 +1000 Subject: [PATCH] Dos updates --- README.md | 183 +++++++++++++++++++++---------------- docs/MentalModelOmnibus.md | 42 +++++---- 2 files changed, 132 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index dd4bed8..9a45925 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ Perhaps: In this space, re-frame is very old, hopefully in a Gandalf kind of way. First designed in Dec 2014, it even slightly pre-dates the official Elm Architecture, although thankfully we were influenced by early-Elm concepts like `foldp` and `lift`, as well as - terrific Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then, + Clojure projects like [Pedestal App], [Om] and [Hoplon]. Since then, re-frame has pioneered ideas like event handler middleware, coeffect accretion, and de-duplicated signal graphs. -5. Which leads us to the most important point: **re-frame is impressively buzzword compliant**. It has reactivity, +5. Which brings us to the most important point: **re-frame is impressively buzzword compliant**. It has reactivity, unidirectional data flow, pristinely pure functions, interceptors, coeffects, conveyor belts, statechart-friendliness (FSM) and claims an immaculate hammock conception. It also has a charming @@ -129,11 +129,17 @@ you to understand re-frame, is **practically proof** it does physics. Computationally, each iteration of the loop involves a six domino cascade. -One domino triggers the next, which triggers the next, et cetera, until we are -back at the beginning of the loop, whereupon the dominoes spring to attention +One domino triggers the next, which triggers the next, et cetera, boom, boom, boom, until we are +back at the beginning of the loop, and the dominoes spring to attention again, ready for the next iteration of the same cascade. -The six dominoes are ... +The six dominoes are: +1. Event dispatch +2. Event handling +3. Effect handling +4. Query +5. View +6. DOM ### 1st Domino - Event Dispatch @@ -243,19 +249,7 @@ This is the step in which the hiccup-formatted "descriptions of required DOM", returned by the view functions of Domino 5, are made real. The browser DOM nodes are mutated. - -### A Cascade Of Simple Functions - -**Each of the dominoes you write are simple, pure functions** which -can be described, understood and -tested independently. They take data, transform it and return new data. - -The loop itself is very mechanical in operation. -So, there's a regularity, simplicity and -certainty to how a re-frame app goes about its business, -which leads, in turn, to an ease in reasoning and debugging. - -### Managing mutation +## Managing mutation The two sub-cascades 1-2-3 and 4-5-6 have a similar structure. @@ -266,12 +260,22 @@ the last domino which does the dirty work and realises these descriptions. In both cases, you don't need to worry yourself about this dirty work. re-frame looks after those dominoes. -## Code Fragments For The Dominos +### A Cascade Of Simple Functions + +**You'll (mostly) be writing pure functions** which +can be described, understood and +tested independently. They take data, transform it and return new data. + +The loop itself is mechanical and predictable in operation. +So, there's a regularity to how a re-frame app goes about its business, +which leads, in turn, to an ease in reasoning and debugging. + +## The Dominoes Again - With Code Fragments -So that was the view of re-frame from 60,000 feet. We'll now shift to 30,000 feet -and look again at each domino, but this time with code fragments. +So that was the view of re-frame from 60,000 feet. We'll now shift down to 30,000 feet +and look again at each domino, but this time with code fragments. **Imagine:** we're working on a SPA which displays a list of items. You have just clicked the "delete" button next to the 3rd item in the list. @@ -284,26 +288,33 @@ to completely grok the terse code presented below. We're still at 30,000 feet. D ### Code For Domino 1 -The delete button for that 3rd item will have an `on-click` handler (function) which looks -like this: +The delete button for that 3rd item will look like this: ```clj - #(re-frame.core/dispatch [:delete-item 2486]) +(defn delete-button + [item-id] + [:div.garbage-bin + :on-click #(re-frame.core/dispatch [:delete-item item-id])]) ``` -`dispatch` emits an `event`. +That the `on-click` handler uses `re-frame.core/dispatch` to emit an `event`. A re-frame `event` is a vector and, in this case, -it has 2 elements: `[:delete-item 2486]`. The first element, -`:delete-item`, is the kind of event. The rest is optional, further data about the -`event` - in this case, my made-up id, `2486`, for the item to delete. +it has 2 elements: `[:delete-item 2486]` (where `2486` in the made-up id for that 3rd item). + +The first element of an event vector, +`:delete-item`, is the kind of event. The rest is optional, useful data about the +`event`. + +Events express intent in a domain specific (app specific) way. +They are the language of your re-frame system. ### Code For Domino 2 -An `event handler` (function), `h`, is called to +An `event handler` (function), called say `h`, is called to compute the `effect` of the event `[:delete-item 2486]`. -Earlier, on program startup, `h` would have been -registered for handling `:delete-item` `events` like this: +On app startup, `re-frame.core/reg-event-fx` would have been used to +register this `h` as the handler for `:delete-item` events, like this: ```clj (re-frame.core/reg-event-fx ;; a part of the re-frame API :delete-item ;; the kind of event @@ -312,25 +323,34 @@ registered for handling `:delete-item` `events` like this: `h` is written to take two arguments: 1. a `coeffects` map which contains the current state of the world (including app state) - 2. the `event` - -`h` returns a map of `effects` - a description -of how the world should be changed by the event. + 2. the `event` to handle + +It is the job of `h` to compute how the world should be changed by the event, and +it returns a map of `effects` - a description of the those changes. Here's a sketch (we are at 30,000 feet): ```clj (defn h - [{:keys [db]} event] ;; args: db from coeffect, event - (let [item-id (second event)] ;; extract id from event vector - {:db (dissoc-in db [:items item-id])})) ;; effect is change db + [coeffects event] ;; args: db from coeffect, event + (let [item-id (second event) ;; extract id from event vector + db (:db coeffects) ;; extract the current application state + {:db (dissoc-in db [:items item-id])})) ;; effect is change app state ``` -re-frame has ways (beyond us here) to inject necessary aspects +re-frame has ways (described in later tutorials) to inject necessary aspects of the world into that first `coeffects` argument (map). Different -event handlers need to know different things about the world -in order to get their job done. But current "application state" -is one aspect of the world which is invariably needed, and it is made -available by default in the `:db` key. +event handlers need to different "things" to get their job done. But +current "application state" is one aspect of the world which is +invariably needed, and it is made available by default in the `:db` key. + +BTW, here more idiomatic rewrite of `h` which uses "destructuring": + +```clj +(defn h + [{:keys [db]} [_ item-id]] ;; <--- new: obtain db and id directly + {:db (dissoc-in db [:items item-id])}) ;; same as before +``` + ### Code For Domino 3 @@ -338,7 +358,7 @@ An `effect handler` (function) actions the `effects` returned by `h`. Here's what `h` returned: ```clj -{:db (dissoc-in db [:items 2486])} +{:db (dissoc-in db [:items 2486])} ;; db is a map of some structure ``` Each key of the map identifies one kind of `effect`, and the value for that key supplies further details. @@ -350,52 +370,61 @@ This update of "app state" is a mutative step, facilitated by re-frame which has a built-in `effects handler` for the `:db` effect. Why the name `:db`? Well, re-frame sees "app state" as something of an in-memory -database. More on that soon. +database. More on this is a following tutorial. Just to be clear, if `h` had returned: ```clj {:wear {:pants "velour flares" :belt false} :tweet "Okay, yes, I am Satoshi. #coverblown"} ``` -Then the two effects handlers registered for `:wear` and `:tweet` would -be called in this domino to action those two effects. And, no, re-frame +Then, the two effects handlers registered for `:wear` and `:tweet` would +be called to action those two effects. And, no, re-frame does not supply standard effect handlers for either, so you would have had to have written them yourself (see how in a later tutorial). ### Code For Domino 4 -Because an effect handler just updated "app state", -a query (function) over this app state is called automatically (reactively), -itself computing the list of items. +Because an effect handler just mutated "application state", +a query (function) over this app state is automatically called (reactively). +This query (function) computes "a materialised view" of the +application state - a version of the application state which is useful to +the next domino, 5. + +Remember, we are in `v = f(s)`, and this domino is about delivering the right +data to later functions (domino 5) which compute DOM. + +Now, in this particular case, the query function is pretty trivial. Because the items are stored in app state, there's not a lot -to compute in this case. This -query function acts more like an extractor or accessor: +to compute and, instead, it acts more like an extractor or accessor, +just plucking the list of items out of application state: ```clj (defn query-fn - [db _] ;; db is current app state + [db v] ;; db is current app state, v the query vector (:items db)) ;; not much of a materialised view ``` On program startup, such a `query-fn` must be associated with a `query-id`, -(for reasons obvious in the next domino) like this: +(so it can be used via `subscribe` in the next domino) using `re-frame.core/reg-sub`, +like this: ```clj (re-frame.core/reg-sub ;; part of the re-frame API :query-items ;; query id query-fn) ;; query fn ``` -Which says "if you see a query (subscribe) for `:query-items`, +Which says "if you see a `(subscribe [:query-items])`, then use `query-fn` to compute it". ### Code For Domino 5 Because the query function for `:query-items` just re-computed a new value, -any view (function) which subscribes to `:query-items` -is called automatically (reactively) to re-compute DOM. +any view (function) which uses a `(subscribe [:query-items])` +is called automatically (reactively) to re-compute new DOM. View functions compute a data structure, in hiccup format, describing -the DOM nodes required. In this case, there will be no DOM nodes -for the now-deleted item, obviously, but otherwise the same DOM as last time. +the DOM nodes required. In this case, the view functions will *not* be generating +hiccup for the now-deleted item obviously but, other than this, +the hiccup computed will be the same as last time. ```clj (defn items-view @@ -404,8 +433,20 @@ for the now-deleted item, obviously, but otherwise the same DOM as last time. [:div (map item-render @items)])) ;; assume item-render already written ``` -Notice how `items` is "sourced" from "app state" via `subscribe`. -It is called with a query id to identify what data it needs. +Notice how `items` is "sourced" from "app state" via `re-frame.core/subscribe`. +It is called with a vector argument, and the first element of that vector is +a query-id which identifies the "materialised view" required. + +Note: `subscribe` queries can be parameterised. So, in real world apps +you might have this:
+ `(subscribe [:items "blue"])` + +The vector identifies, first, the query, and then +supplies further arguments. You could think of that as representing `select * from Items where colour="blue"`. + +Except there's no SQL available and you would be the one to implement +the more sophisticated `query-fn` to handle those +further arguments. More in later tutorials. ### Code For Domino 6 @@ -443,7 +484,7 @@ When building a re-frame app, you: - write Reagent view functions (view layer) (domino 5) -## It is mature and proven in the large +## re-frame is mature and proven in the large re-frame was released in early 2015, and has since [been](https://www.fullcontact.com) successfully @@ -474,28 +515,16 @@ and useful 3rd party libraries. ## Where Do I Go Next? -**At this point you -already know 50% of re-frame.** There's detail to fill in, for sure, -but the core concepts, and even basic coding techniques, are now known to you. +**At this point you already know 50% of re-frame.** The full [docs are here](/docs). -Next you need to read the other three articles in the [Introduction section](/docs#introduction): - -* [Application State](/docs/ApplicationState.md) -* [Code Walkthrough](/docs/CodeWalkthrough.md) -* [Mental Model Omnibus](/docs/MentalModelOmnibus.md) - -This will push your knowledge to about 70%. The -final 30% will come incrementally with use, and by reading the other -tutorials (of which there are a few). - -You can also experiment with these two examples:
+There are two example apps to play with:
https://github.com/Day8/re-frame/tree/master/examples Use a template to create your own project:
Client only: https://github.com/Day8/re-frame-template
Full Stack: http://www.luminusweb.net/ -Use these resources:
+And please be sure to review these further resources:
https://github.com/Day8/re-frame/blob/develop/docs/External-Resources.md ### T-Shirt Unlocked diff --git a/docs/MentalModelOmnibus.md b/docs/MentalModelOmnibus.md index 51ccc17..edfee69 100644 --- a/docs/MentalModelOmnibus.md +++ b/docs/MentalModelOmnibus.md @@ -102,17 +102,22 @@ Sincerely, The Self-appointed President of the Cursor Skeptic's Society. ## DSLs and VMs -`Events` are central to re-frame's architecture. Nothing happens without a dispatched event. +`Events` are cardinal to re-frame - they are a fundamental organising principle. -Every app will have a different `events`. Indeed, part of your job will be to -design exactly the right set of them. For your app,`events` will be the "language of the system". -They capture intent (generally the user's intent). They provide the eloquence. +Every re-frame app will have a different set of `events`. Your job is +to design exactly the right ones for any given app you build. These `events` +will be the "language of the system". They'll represent intent +(generally the user's). They'll provide the eloquence. And they are data. -Here's a collection of events from a drawing app: +Imagine we created a drawing application. And then we allowed +someone to use our application, and captured, into a collection, +the events caused by that user's actions (button clicks, drags, etc). + +The collection of events might look like this: ```cljs -(def events +(def collected-events [ [:clear] [:new :triangle 1 2 3] @@ -122,25 +127,30 @@ Here's a collection of events from a drawing app: ]) ``` -I'd like you to look upon that collection as you would the following assembler: +Now, consider the following assembly instructions: ```asm mov eax, ebx sub eax, 216 mov BYTE PTR [ebx], 2 ``` -Assembler instructions are data, right? They have to be "executed" by a machine. I'd -like you to look back at the collection of events above the same way. +Assembly instructions are data, right? Data which happens to be "executable" +by the right machine. An x86 machine in the case above. -Mental Model: - - Events are the assembly language of your app. They are instructions. - - These instructions are data. One after another gets executed by your functioning app. - - Collectively, the events you design form a Domain Specific Language (DSL). The language of your system. - - And, collectively, the Event Handlers you register create the Virtual Machine (VM) on which this DSL executes. +I'd like you to now look back at that collection of events in the +same way - data which can be executed - by the right machine. -> So, data is executed by the re-frame VM you create. Which is all data oriented and Clojurian. +Wait. What machine? Well, the Event Handlers you register collectively implement +the "machine" on which these Instructions execute. -I find James Reeves' talks to be excellent (video): [Transparency through data](https://www.youtube.com/watch?v=zznwKCifC1A) +Summary: + - Events are the assembly language of your app. + - These instructions collectively form a Domain Specific Language (DSL). The language of your system. + - 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. + +On the subject of DSLs, I'd recommend James Reeves' talk (video): [Transparency through data](https://www.youtube.com/watch?v=zznwKCifC1A) ## It does Event Sourcing