Last active
December 30, 2015 20:29
-
-
Save aamedina/7881544 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ;; the runtime is centered around three fundamental queues | |
| ;; 1) the input queue - your app sends messages to the input queue which uniquely identify a piece of state to update according | |
| ;; to the message params. These parameters are specified as part of the function definition, so there's no need to search around | |
| ;; for "configs" or "spec maps" anywhere in the code - app behavior is specified in the functions themselves. | |
| ;; 2) the output queue - this queue gets all messages that need to be processed outside of the application runtime - | |
| ;; common side effects that are relayed through this queue include DOM manipulation and Ajax requests | |
| ;; 3) the app model queue - this queue propagates renderable changes in the data model to the view (the actual frontend) | |
| ;; through the use of fine grained messages, called rendering deltas | |
| ;; test messages | |
| (def default-msg | |
| {:type :default :path [:nil :**] :value "hallo"}) | |
| (def swap-msg | |
| {:type :swap :path [:other-counters "abc"] :value 42}) | |
| (def inc-msg | |
| {:type :inc :path [:my-counter]}) | |
| ;; test transforms | |
| ;; multimethod syntax is as follows | |
| ;; (defmethod multimethod-name dispatch-val arguments & function-body) | |
| ;; the dispatch-val is dispatched according to an arbitrary function defined with the methods corresponding defmulti - | |
| ;; this is a backend/non-application dev concern, so it's not directly applicable for dev user facing code to think about. | |
| ;; `(defmulti transform (fn [state message] [(:type message) (:path message)])` | |
| ;; from this definition you can see that transforms are dispatched based on the type and path of the input message | |
| ;; this method will accept messages with {:type :inc, :path [:my-counter]} | |
| (defmethod transform [:inc [:my-counter]] | |
| [state _] ;; _ is an idiom in clojure to denote arguments we don't care to give local bindings | |
| ((fnil inc 0) state)) | |
| ;; fnil is a function `(fnil function default-value)` that returns a default value when the input is nil | |
| ;; we don't care about the state here, so now the state is _ and we take :value from the message | |
| ;; note the use of the wildcard :** - this transform will be called for any route with :type :swap | |
| (defmethod transform [:swap [:**]] | |
| [_ message] | |
| (:value message)) | |
| ;; derives are special, they take an input of paths to match against and create a new route at the output-path | |
| ;; syntax: [input-paths, output-path, input-spec - namely, how do you want the aggregated input to be specified, | |
| ;; :vals returns a sequence of the values, which in this case we're reducing | |
| (defmethod derives [#{[:my-counter] | |
| [:other-counters :*]} [:total-count] :vals] | |
| [message state nums] | |
| (reduce + nums)) | |
| (defmethod derives [#{[:my-counter] | |
| [:other-counters :*]} [:max-count] :vals] | |
| [message old-value nums] | |
| (apply max (or old-value 0) nums)) | |
| (defmethod derives [{[:my-counter] :nums | |
| [:other-counters :*] :nums | |
| [:total-count] :total} [:average-count] :map] | |
| [message old-value {:keys [nums total]}] | |
| (/ total (count nums))) | |
| ;; this is an example of post-processing method, which is called after the data-model has been updated, but before the DOM | |
| ;; has been updated. Here we make sure that the :value being rendered to the DOM for the [:average-count] path is | |
| ;; rounded properly. This has all sorts of nifty uses - like formatting dates the way you want, currency, languages, etc. | |
| ;; the possibilities are endless! | |
| (defmethod post-process [:value [:average-count]] | |
| [[op path n]] | |
| (letfn [(round [n places] | |
| (let [p (Math/pow 10 places)] | |
| (/ (Math/round (* p n)) p)))] | |
| [[op path (round n 2)]])) | |
| ;; incase you're wondering how dependencies are handled and resolved at runtime - look no further! | |
| ;; there is a runtime dependency graph maintained by the dataflow engine which correctly orders every relationship | |
| ;; between every node in the data-model. | |
| ;; this is the relational programming part of things - | |
| ;; here's what the run time dependency graph would look like for the aforementioned functions | |
| (require [foundation.app.dependency :as d]) | |
| (def counter-dependencies | |
| (-> (d/graph) | |
| (d/depend [:my-counter] nil) | |
| (d/depend [:other-counters :*] nil) | |
| (d/depend [:total-count] [:my-counter]) | |
| (d/depend [:total-count] [:other-counters :*]) | |
| (d/depend [:max-count] [:my-counter]) | |
| (d/depend [:max-count] [:other-counters :*]) | |
| (d/depend [:average-count] [:my-counter]) | |
| (d/depend [:average-count] [:total-count]) | |
| (d/depend [:average-count] [:other-counters :*]))) | |
| (comment | |
| ;; imagine [:my-counter] is being rendered as a button in the DOM. When clicked, the message is dispatched and the data-model | |
| ;; is updated. How do we make sure we update -every single- node which depends on the value at :my-counter? | |
| ;; enter - elementary graph theory! | |
| (d/transitive-dependents counter-dependencies [:my-counter]) | |
| #{[:total-count] [:average-count] [:max-count]} | |
| ;; so, one simple call to get the transitive dependents of any path - this is a list of paths which are going to need new values | |
| ;; so how do we derive new values for these? simple! calculate their transitive dependencies! | |
| ;; [:my-counter] has no dependencies, which means it is "root" value. It does not derive its value from anything else in the | |
| ;; data-model. | |
| (d/transitive-dependencies counter-dependencies [:my-counter]) | |
| #{} | |
| ;; Average count on the other hand was one of the transitive dependents of [:my-counter]. | |
| ;; so to calculate its new value, we just have to reduce the state of all of its dependencies! | |
| ;; complicated code made simple with knowledge - (reduce depends graph [:average-count]) | |
| (d/transitive-dependencies counter-dependencies [:average-count]) | |
| #{[:other-counters :*] [:total-count] [:my-counter]} | |
| ) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment