lwcells icon indicating copy to clipboard operation
lwcells copied to clipboard

Light Weight Cells

#+TITLE:LWCELLS - Light Weight Cells ~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.

  • Tutorial Basic usage: #+BEGIN_SRC lisp (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 #+END_SRC 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: #+BEGIN_SRC lisp (defcell cell-1 1) (defcell cell-2 2) (defcell cell-3 (+ cell-1 cell-2)) #+END_SRC ~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: #+BEGIN_SRC lisp (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 ...) #+END_SRC

    Hackers' note: Under the hood, observers are implemented as ~observer-cell~, a special kind of ~cell~ whose ~cell-ins~ never change.

    Convenience for CLOS: #+BEGIN_SRC lisp (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 #+END_SRC 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.

  • Related works

    • [[https://github.com/kennytilton/cells][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!
    • [[https://github.com/hu-dwim/hu.dwim.computed-class][computed-class]] :: A bit more complicated than ~lwcells~. The syntax is also a bit less sweet, requiring lots of ~computed-as~ everywhere.
  • Documentation Read the source code! There isn't lots of code.