Skip to content

Cell Reference

Kenneth Tilton edited this page Apr 7, 2023 · 2 revisions

Synopsis

Speaking loosely, a cell is a reactive property of a Matrix model. A model is a CLJ map with meta suitably provisioned to support MX reactive/dataflow behavior. Cells may optionally be expressed as functions of other cells.

A simple example involving just one model, where:

  • make produces the model map;
  • cI produces an "I"nput cell, whose value we will be able to alter imperatively;
  • cF produces a "F"ormulaic cell, which can compute its value using other cell values; and
  • mget and mswap! provide the expected functionality, integrated with the MX engine:
(let [md (md/make
               :height 2 ;; meters
               :weight (cI 80) ;; kg
               :bmi (cF (/ (mget me :weight)
                          (Math/pow (mget me :height) 2)))
               :weight-status (cF (let [bmi (mget me :bmi)]
                                    (cond
                                      (< bmi 18.5) :underweight
                                      (<= 18.5 bmi 24.9) :healthy
                                      (<= 25.0 bmi 29.9) :overweight
                                      :else :obese))))]
      (is (= (mget md :bmi) 20.0))
      (is (= (mget md :weight-status) :healthy))
      (mswap! md :weight + 20)
      (is (= (mget md :bmi) 25.0))
      (is (= (mget md :weight-status) :overweight)))

n.b. Cycles in such dependencies are detected at run time and throw an exception, but see our "Possibilities" appendix.

Implementation-wise, a cell is a data structure, another CLJ map, associated with a property of a model instance. When a cell changes, dependent cells are recalculated, recursively propagating until the state tree is consistent. Think "spreadsheet".

Unlike a spreadsheet cell, MX cells can be assigned "watch" functions. These fire as soon as a cell sees a new value, and are expected to act outside the MX reactive flow. However, if wrapped in a with-change macro, watch functions can mutate other input cells.

That describes the core cell behavior. Next we look at specific cell options and consider how each refines cell behavior.

Input cell options

Here are the options expected/allowed by make-cell, the non-formulaic cell fabricator, with the more common options first:

Option Description
value the initial value for the property. Default nil;
watch an optional function with signature (fn [prop me new-value prior-value cell]...)
ephemeral? if true, the cell value, after being set and propagated, is reset to nil without propagating. This exists to support event-like state;
on-quiesce Optional. A function (fn [cell]...) to be called when a cell is being disconnected from the matrix
unchanged-if default is CLJ =. Optional function with signature (fn [new-value prior-value]...)
prop the property name. Optional. Default nil, but populated automatically by the model make function;
input? indicates if this value be altered by imperative code. Default true;
debug a keyword that can be used by the MX debug tools to filter tracing

Formulaic cell options

Same as for input cells, with these additions:

Option Description
rule aka "formula". A function (fn [cell] ...) to compute the property value. See below for cF macros that unpack the cell usefully.
input? Default false. If true, the formula is evaluated exactly once, without establishing dependencies, and then can be altered as if it were an input cell. This is needed where a cell destined to be an input requires initialization from other run-time state. Analog is an OO class constructor function initializing an instance based on other instance parameters.
lazy WIP. Possible values true (alias :always), :until-asked (then eager), :once-asked (before then eager), false/nil.
async? RSN. Eventually will process a future (in some form) produced by the formula in place, assuming the value nil until the async value is obtained, then msetting that value into the cell as an external change to an input.

cF macros

Here are some handy macros for formulaic properties of MX models:

Macro Description
cF Syntax: (cf _rule-body_). The rule body will have access to three anaphors: me, the model; _cache, the prior value, and _cell, the cell instance. On the first evaluation, the _cache will be tiltontec.matrix.api/unbound, currently (gensym "unbound-cell-value").
cF+ Syntax: (cF+ [ & _cell-options_ ] _rule-body_), where cell-options are alternating keys and values.
cFn Syntax: (cf _rule-body_). Equivalent to (cF+ [:input? true] _rule_body).

Appendix: Possibilities

We collect here open items, or ideas worth exploring. The evolution of Cells has almost invariably been driven by actual application requirements, so not all possible features will be implemented when conceived in a vacuum. Instead, we record them here just to let folks know we have them on radar.

Cyclic cells

What if we want to write some rules such that A <- B <- A?

Currently that throws an exception at run time, but we can conceive supporting a circuit breaker option on a cell, which would be a function to decide a value should evaluation circle back to the same cell. This is a great case, btw, of something with a non-obvious implementation, so we want to wait for an authentic use case before acting.