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*) ; => 6Hackers’ 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*) ; => 10Hackers’ 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-aseverywhere.
Read the source code! There isn’t lots of code.