LWCELLS
is a dataflow extension to Common Lisp. It maintains a
consistent state of cells according to functions specifying their
relation. LWCELLS
is designed to be simple, clean, compositional
and flexible.
Basic usage:
(use-package :lwcells)
(defparameter *cell-1* (cell 1))
(defparameter *cell-2* (cell 2))
(defparameter *cell-3* (cell (+ (cell-ref *cell-1*) (cell-ref *cell-2*))))
(cell-ref *cell-3*) ; => 3
(setf (cell-ref *cell-2*) 5)
(cell-ref *cell-3*) ; => 6
Hackers’ note: The body of each cell
form is wrapped into a
function and assigned to the cell-function
slot of constructed
cell
object (actually, a lazy-cell
). At any moment, cell
with
non-nil cell-function
will ensure their observed value matches the
result of running cell-function
. If you explicitly (setf
cell-ref)
a cell, its cell-function
is removed to prevent it from
running again and overwrite the explicitly assigned value.
Fancier syntax sugar that does roughly the same thing as above:
(defcell *cell-1* 1)
(defcell *cell-2* 2)
(defcell *cell-3* (+ *cell-1* *cell-2*))
defcell
have the additional nicety that when you redefine a cell,
if an existing cell object is to be overwritten, such cell is also
deactivated so that observers (to be explained!) defined on the old
cell stop making noises.
Hackers’ note: Under the hood, this stores the cell objects in
variable like *cell-1*-cell
, and defines symbols like *cell-1*
themselves as symbol macros.
There’s also let-cell
and let*-cell
that does the similiar for
lexical variables.
Observers:
(defcell *cell-1* 1)
(defcell *cell-2* 2)
(defcell *cell-3* (+ *cell-1* *cell-2*))
(defun report-assign (cell)
(let ((*print-circle* t))
(format t "Assigning ~a to ~a." (cell-ref cell) cell)))
(add-observer *cell-3*-cell 'report-assign)
(setf *cell-2* 4)
;; => Assigning 5 to #1=#S(CELL ...)
Hackers’ note: Under the hood, observers are implemented as observer-cell
,
a special kind of cell
whose cell-ins
never change.
Convenience for CLOS:
(defmodel item () ((weight :cell 0 :initarg :weight)))
(defmodel container ()
((items :cell nil)
(weight :cell (reduce #'+ (items self) :key #'weight))))
(defvar *container* (make-instance 'container))
(weight *container*) ; => 0
(push (make-instance 'item :weight 10) (items *container*))
(weight *container*) ; => 10
Hackers’ note: Under the hood, defmodel
expand into a
defclass
, an initialize-instance
method to store cell objects
into slots, and some accessors method to read/write values from cell
objects in the slots. You can then use the accessors method to
transparently access reactive values. To get and manipulate the
underlying cell objects, use slot-value
.
defmodel
doesn’t use any MOP magic, and is fully compatible with
standard CLOS classes.
- cells
- This is very powerful, but also very complicated. AFAIU cells are also always attached to slots and don’t exist on their own. I might be wrong – I don’t understand all the code!
- computed-class
-
A bit more complicated than
lwcells
. The syntax is also a bit less sweet, requiring lots ofcomputed-as
everywhere.
Read the source code! There isn’t lots of code.