Skip to content

Instantly share code, notes, and snippets.

@mike-thompson-day8
Last active July 17, 2016 23:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-thompson-day8/9481b281584659d066cacc25a955ac62 to your computer and use it in GitHub Desktop.
Save mike-thompson-day8/9481b281584659d066cacc25a955ac62 to your computer and use it in GitHub Desktop.
> This Gist is out of date. It has been superseded by
> https://gist.github.com/mike-thompson-day8/d61692451166e206a9c43771c8b389fc
;; Version 2
;; WARNING: this code is untested. It probably doesn't even compile.
;; It is meant to be indicative of how `regsub` might work/look.
;; I've taken the todomvc subscriptions and I've rewritten them using a new mythical
;; macro `regsub`.
;; Existing: https://github.com/Day8/re-frame/blob/4b931fe67208c10ae960c683fd837b03c38f88b0/examples/todomvc/src/todomvc/subs.cljs
(ns todomvc.subs
(:require [re-frame.core :refer [regsub]]))
;; The goal is to allow us to write subscription handlers without ever
;; using `reaction` directly.
;; Here we register a simple handler.
(regsub :showing
(fn [db _] ;; db, is the value in app-db
(:showing db))) ;; I repeat: db is a value. Not a ratom. And this fn does not return a reaction, just a value.
;; For educational purposes, the following, similar registration is done in two steps.
;; First, we `defn` a pure function. Then, we use `regsub` to register this pure function.
;; Two steps. This is different to the first handler, which was one step.
(defn sorted-todos
[db _]
(:todos db))
(regsub :sorted-todos sorted-todos)
;; -------------------------------------------------------------------------------------
;; Beyond Simple Handlers
;;
;; A subscription handler is a function which is re-run when its input signals
;; change. Each time it is rerun, it produces a new output (return value).
;;
;; In the simplest case, app-db is the only input signal, as was the case in the two
;; simple subscriptions above. But many subscriptions are not directly dependent on
;; app-db, and instead, depend on a value derived from app-db.
;;
;; Such handlers represent "intermediate nodes" in a signal graph. New values emanate
;; from app-db, and flow out through a signal graph, into and out of these intermediate
;; nodes, before a leaf subscription delivers data into views which render data as hiccup.
;;
;; When writing the handler for an intermediate node, you must nominate one or more
;; input signals (typically one or two).
;;
;; regsub allows you to supply:
;; - a function which returns the input signals. It can return either a single signal or
;; a vector of signals, or a map of where the values are the signals.
;; - a function which does the computation. It takes input values and produces a new
;; derived value.
;;
;; In the two simple examples at the top, we only supplied the 2nd of these functions.
;; But now we are dealing with intermediate nodes, we'll need to provide both fns.
;;
(regsub :todos
;; This function returns the input signals.
;; In this case, it returns a single signal.
;; Although not required in this example, it is called with
;; the query-v (the 2nd dyn-v parameter is ignored via a `_` ... it is
;; only ever used in very advanced cases).
(fn [query-v _]
(subscribe [:sorted-tods])) ;; returns a single input signal
;; This fn next does the computation. Data values in, derived data out.
;; It is identical in nature to the two simple subscription handlers up at the top.
;; Except they took the value in app-db as their first argument and, instead,
;; this function takes the value delivered by another input signal: (subscribe [:sorted-todos])
;;
;; Handlers can take 3 parameters. The 2nd one is the query-v (the query vector argument
;; to the "subscribe") and the 3rd one is for advanced cases where a further vector is signals
;; is supplied.
(fn [sorted-todos query-v _]
(vals sorted-todos)))
;; So here we define the handler for another intermediate node.
;; This time the computation involves two input signals.
;; As a result note:
;; - the first function (which returns the signals, returns a 2-vector)
;; - the second function (which is the computation, destructures this 2-vector as its first parameter)
(regsub :visible-todos
(fn [query-v _] ;; returns a vector of two signals.
[(subscribe [:todos])
(subscribe [:showing])])
(fn [[todos showing] _] ;; that 1st parameter is a 2-vector of values
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos))))
;; -------------------------------------------------------------------------------------
;; HEY, WAIT ON
;;
;; How did those two simple registrations at the top work, I hear you ask?
;; We supplied only one function in those registrations, not two?
;; I'm glad you asked.
;; You see, when the signal-returning-fn is omitted, regsub provides a default.
;; And it loks like this:
;; (fn [_ _] re-frame.db/app-db)
;; You can see that it returns one signal, and that signal is app-db itself.
;;
;; So that's why those two simple registrations didn't provide a signal-fn, but they
;; still got the value in app-db supplied as that first parameter.
;; -------------------------------------------------------------------------------------
;; SUGAR ?
;; Now for some syntactic sugar...
;; The purpose of the sugar is to remove boilerplate noise. To distill to the essential
;; in 90% of cases.
;; Is this a good idea?
;; If it is a good idea, is this good syntax?
;; Because it is so common to nominate 1 or more input signals,
;; regsub provides some macro sugar so you can nominate a very minimal
;; vector of input signals. The 1st function is not needed.
;; Here is the example above rewritten using the sugar.
(regsub :visible-todos
:<- [:todos]
:<- [:showing]
(fn [[todos showing] _]
(let [filter-fn (case showing
:active (complement :done)
:done :done
:all identity)]
(filter filter-fn todos))))
(regsub :completed-count
:<- [:todos]
(fn [todos _]
(count (filter :done todos))))
(regsub :footer-stats ;; different from original. Now does not return showing
:<- [:todos]
:<- [:completed-count]
(fn [[todos completed] _]
[(- (count todos) completed) completed]))
Copy link

ghost commented Jun 10, 2016

I think this is a neat idea. Not quite sure I follow lines 97-99 (the last :visible-todos regsub)...isn't the point of the sugar macro that the subscribe func isnt' required? Perhaps this is your intention and it's just not clear from the comments.

If I might suggest something:

"Positional arguments are a form of coupling." - Rich Hickey

I'm a big believer in this concept, and I find my cycle time is consistently better when developing under this notion. While it arguably doesn't apply in this case because the only one invoking this function is the framework, and it's not an exposed API– it's one of the very few things I don't enjoy about reframe.

@vspinu
Copy link

vspinu commented Jul 6, 2016

Why todos in line 127 is not a vector [todos]? Such a contextual syntax makes reading and writing code harder as each time you have to remember the exact convention.

As I have already suggested in the main thread, it might be possible to track the signal graph without explicitely declaring it. It's a macro after all and it should be able figure out which signals are used from the body of the call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment