missionary 2024-11-11

Howzit Guys. I am trying to learn Missionary The Hard Way by writing a toy https://github.com/hoplon/javelin spreadsheet program, where each cell has a "formula" that can be a Clojure expression or function (which could maybe do web requests, ideally using Missionary). I am aware of Tesserae, but I want to use Missionary primitives maybe with Electric to display things later. The output of a "cell" would be (apply cell-formula <input-signals) where inputs are the signals from other cells referenced in its formula. So probably each cell will have {:keys [formula output]} where :output is the result of computing the formula using Missionary using the :output of other referenced cells, e.g. c123 for cell 123. Based on the README examples, I can hard-code some atoms and a simple computation (+) using m/latest, e.g.

(def !c1 (atom 10))
(def !c2 (atom 20))
(def !c3 (atom nil))

(def cell-task
  (let [<c1 (m/signal (m/watch !c1))
        <c2 (m/signal (m/watch !c2))
        arbitrary-c3-formula (fn [& args] (reduce + args))
        <c3 (m/signal (m/latest arbitrary-c3-formula <c1 <c2))]
    (m/reduce (fn [_ x]
                (prn 'x x)
                (reset! !c3 x))
              nil <c3)))

(def dispose!
  (cell-task
    #(prn ::success %)
    #(prn ::crash %)))

(reset! !c2 57)
@!c3
I'm not sure if I'm using m/latest correctly here, but I want to support arbitrary "formulas" where any references to other cell IDs (e.g. c123) are replaced (at macro-time? runtime?) with the appropriate Missionary signal/computation. I can do the parsing with a macro and have some <cN convention for cell numbering. Assuming I detect cycles beforehand, I'm hoping Missionary can handle the async dependencies for me and block appropriately. Hence, if we assume that the arbitrary formula for cell 3 (`c3`) is the simple formula (fn [c1 c2] (+ c1 c2)), I should be bale to construct some let bindings for all known cells and replace symbols like c1 and c2 symbols with the appropriate Missionary signals, right? Ideally of course this should come from Datalevin (like Tesserae). I can parse formulas into Clojure expressions and replace symbols in a macro, but I need some help understanding how to pass these things around to construct a Missionary (task? or flow?) based on the dependency graph between cells so that's flexible. I'm not even sure if I can do this at runtime, but I. There is probably some overlap with what Electric is doing internally. Is there a pattern for "subscribing" to other signals where you can add signals during runtime and recompute the computation graph? I assume that at minimum, I need to order the bindings topographically so that dependent cell formulas only reference previously defined signals. Any help would be appreciated.

Missionary signals are essentially a generalization of cells. The argument to m/signal can be an arbitrary effect expressed as a (continuous) flow : • m/watch to get the current state of a mutable reference (input cells) • m/latest to derive state from other ones with an arbitrary function (formula cells) Alternatively, you can use m/cp to depend on one input or another based on a condition. The graph topology is now dynamic.

(m/signal
  (m/cp
    (if (m/?< c1)
      [:c2 (m/?< c2)]
      [:c3 (m/?< c3)])))
If the state can be expressed as successive reductions of an event stream, you can use m/reductions.
;; the state starts nil then becomes the result of the task when it completes
(defn task-result [task]
  (m/reductions {} nil
    (m/ap (m/? task))))
Now assuming you can express your database query as a task, you can use it in m/cp to keep a query result in sync with a variable input : (m/signal (m/cp (m/?< (task-result (db-query (m/?< c1)))))) > Is there a pattern for "subscribing" to other signals where you can add signals during runtime and recompute the computation graph? I assume that at minimum, I need to order the bindings topographically so that dependent cell formulas only reference previously defined signals. Generally speaking, you should not have to think about graph topology and node ordering. The publishers (signals, in this case) are totally ordered (they support compare) and the propagation engine makes sure changes are dispatched consistently with this order. In this order, publishers created in reaction to another publisher are inferior to this parent, and siblings are ordered according to their birth rank. Just create new signals at any time, sub/unsub them with m/cp + m/?< or m/latest, the effect lifecycle will be managed by subscribers.

👍 1

Thanks, I got my spreadsheet working 👍

🔥 1