[cells-gtk-cvs] CVS cells/doc
pdenno
pdenno at common-lisp.net
Wed Jun 7 16:28:58 UTC 2006
Update of /project/cells-gtk/cvsroot/cells/doc
In directory clnet:/tmp/cvs-serv32368/doc
Added Files:
01-Cell-basics.lisp cell-doc.lisp hw.lisp
Log Message:
new files
--- /project/cells-gtk/cvsroot/cells/doc/01-Cell-basics.lisp 2006/06/07 16:28:58 NONE
+++ /project/cells-gtk/cvsroot/cells/doc/01-Cell-basics.lisp 2006/06/07 16:28:58 1.1
;; -*- mode: Lisp; Syntax: Common-Lisp; Package: cellsS -*-
;;;
;;; Copyright (c) 1995,2003 by Kenneth William Tilton.
;;;
;;; Permission is hereby granted, free of charge, to any person obtaining a copy
;;; of this software and associated documentation files (the "Software"), to deal
;;; in the Software without restriction, including without limitation the rights
;;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
;;; copies of the Software, and to permit persons to whom the Software is furnished
;;; to do so, subject to the following conditions:
;;;
;;; The above copyright notice and this permission notice shall be included in
;;; all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
;;; IN THE SOFTWARE.
(in-package :cells)
#|
here is a minimal primer on cells, just enough for you to
keep up with the next tutorial. that will be a substantial project
in which we develop a clos object inspector.
the inspector project will give you a feel for what it is like to
program with cells and cello /after/ you are fluent in the
technology. the intent is not to teach you cello, rather to
motivate your learning it.
so why the primer on cells? if things like c? and cv and def-c-output
do not mean anything to you, the hunh? factor will be overwhelming.
cells
-----
think of a clos slot as a cell in a paper spreadsheet, a financial
modeling tool popular enough to make visi-calc the first business
killer app for microcomputers.
as a child i watched my father toil at home for hours over paper
spreadsheets with pencil and slide rule. after he changed one value,
he had to propagate that change to other cells by first remembering
which other ones included the changed cell in their computation.
then he had to do the calculations for those, erase, enter...
and then repeating that process to propagate those changes in a
cascade across the paper.
visi-calc let my father take the formula he had in mind and
put it in (declare it to) the electronic spreadsheet. then visi-calc
could do the tedious work: recalculating, knowing what to recalculate,
and knowing in what order to recalculate.
cells do for programmers what electronic spreadsheets did for my father.
without cells, clos slots are like cells of a paper spreadsheet.
a single key-down event can cause a cascade of change throughout an
application. the programmer has to arrange for it all to happen,
all in the right order: delete any selected text, insert
the new character, re-wrap the text, update the undo mechanism, revisit
the menu statuses ("cut" is no longer enabled), update the scroll bars,
possibly scroll the window, flag the file as unsaved...
with cells, the programmer looks at program state differently. one
asks, "how could i compute, at any point of runtime, a value for
a given slot of an arbitrary instance, based only on other runtime state
(other slots of other instances)." great fun, by the way, as well as
enforcing good programming practices like encapsulation.
an example will help. consider indeed the state of the "cut" menu item.
in some applications, programmers have a dozen places in their code
where they tend to the status of the cut menu item. one might be:
(defun do-clear (edit-structure)
(when (selected-range edit-structure)
<set up undo>
<toss selected text>
<etc><etc>
(menu-item-enable *edit-cut* nil)
(menu-item-enable *edit-copy* nil)
(menu-item-enable *edit-clear* nil)))
other programmers wait until the user clicks on the edit menu,
then decide just-in-time from program state whether the cut item
should be enabled:
(defmethod prep-for-display ((m edit-menu))
<lotsa other stuff>
(when (typep (focus *app*) 'text-edit-widget)
(menu-item-enable (find :cut (items m) :key #'item-name)
(not (null (selected-range (focus *app*)))))))
this latter programmer is ready for cells, because they
have already shifted from imperative to declarative thinking;
they have learned to write code that works based not on what
has happened lately, but instead only on the current program
state (however it got that way).
the cell programmer writes:
(make-instance 'menu-item
:name :cut
:label "cut"
:cmd-key +control-x+
:actor #'do-cut
:enabled (c? (when (typep (focus *app*) 'text-edit-widget)
(not (null (selected-range (focus *app*)))))))
...and now they can forget the menu item exists as they work
on the rest of the application. the menu-item enabled status
will stay current (correct) as the selected-range changes
and as the focus itself changes as the user moves from field
to field.
that covers the spirit of cells. now let's look at the syntax
and mechanics, with examples you can execute once you have
loaded the cells package. see the read-me.txt file in the
root directory into which the cello software was unzipped.
we'll model a falling stone, where the distance fallen is half
the product of the acceleration (due to gravity) and the
square of the time falling.
|#
(in-package :cells)
(defmodel stone ()
((accel :cell t :initarg :accel :initform 0 :accessor accel)
(time-elapsed :cell t :initarg :time-elapsed
:initform (c-in 0)
:accessor time-elapsed)
(distance :cell t :initarg :distance :initform 0 :accessor distance))
(:default-initargs
:distance (c? (/ (* (accel self)
(expt (time-elapsed self) 2))
2))))
(def-c-output accel ((self stone) new old old-bound-p)
(trc "echo accel" :new new :old old :oldp old-bound-p)) ;; TRC provides print diagnostics
(def-c-output time-elapsed ((self stone)) ;; short form (I'm lazy)
(trc "echo time-elapsed" :new new-value :old old-value :oldp old-value-boundp))
(def-c-output distance ((self stone))
(format t "~&echo distance fallen: ~d feet" new-value))
#|
let's look at non-standard syntax found in the forms above,
in the order in which they appear:
(defmodel ...
defmodel is just a defclass wrapper which also sets up plumbing for cells.
... :cell t ...
without this option, a model instance slot cannot be powered
by a cell (and cell slot access overhead is avoided).
with this option, one can specify what kind of cell
is to be defined: ephemeral, delta or t (normal). we'll leave
those esoteric cell slot types for another tutorial and just
specify t to get normal cells (the ones used 99% of the time).
time-elapsed ... :initform (c-in 0)...
(c-in <value>) allows the cellular slot (or "cell", for short)
to be setf'ed. these are inputs to the dataflow,
which usually flows from c? to c? but has to start somewhere.
since modern interactve applications are event-driven, in
real-world cello apps most cv dataflow inputs are slots closely
corresponding to some system value, such as the position slots
of a cell-powered mouse class. moving on...
a naked value such as the 32 supplied for accel cannot be changed; a
runtime error results from any such attempt. this makes cells faster,
because some plumbing can be skipped: no dependency gets recorded between
the distance traveled and the acceleration. on the other hand, a more
elaborate model might have the acceleration varying according to the distance
between the stone and earth (in which case we get into an advance
topic for another day, namely how to handle circularity.)
next: (:default-initargs
:distance (c? (/ (* (accel self)
(expt (time-elapsed self) 2))
2)
c? associates a rule with a cellular slot (or "cell", for short). any
read operation on another cell (directly or during a function call)
establishes a dependency of distance on that cell -- unless that cell
can never change. why would a cell not be able to change?
cell internals enforce a rule that a cell with a naked value (ie, not wrapped
in cv or c?) cannot be changed by client code (ok, (setf slot-value) is a backdoor).
cell internals enforce this, simply to make possible the optimization
of leaving off the overhead of recording a pointless dependency.
next: (def-c-output...
here is the signature for the def-c-output macro:
(defmacro def-c-output (slotname (&optional (self-arg 'self)
(new-varg 'new-value)
(oldvarg 'old-value)
(oldvargboundp 'old-value-boundp))
&body echo-body) ....)
def-c-output defines a generic method one can specialize on any of the four
parameters. the method gets called when the slot value changes, and during
initial processing by:
(to-be....)
to-be brings a new model instance to life, including calling
any echos defined for cellular slots.
why not just do this in initialize-instance? we build complex
models in the form of a tree of many model instances, any of
which may depend on some other model instance to calculate
some part of its state. models find the one they are curious
about by searching the tree.
this means we cannot just bring a model instance to life at
make-instance time; some cell rule may go looking for another
model instance. we must wait until the instance is
embedded in the larger model tree, then we can kick off to-be.
likewise, when we yank an instance from the larger model we
will call not-to-be on it.
the good news is that unless i am doing little tutorial examples
i never think about calling to-be. trees are implemented in part
by a "kids" (short for "children") cell. the echo on that cell
calls to-be on new kids and not-to-be on kids no longer in the list.
now evaluate the following:
|#
(defparameter *s2* (make-instance 'stone
:accel 32 ;; (constant) feet per second per second
:time-elapsed (c-in 0)))
#|
...and observe:
0> echo accel :new 32 :old nil :oldp nil
0> echo time-elapsed :new 0 :old nil :oldp nil
echo distance fallen: 0 feet
getting back to the output shown above, why echo output on a new instance?
when we call to-be we want the instance to come to life. that means
evaluating every rule so the dependencies get established, and
propagating cell values outside the model (by calling the echo
methods) to make sure the model and outside world (if only the
system display) are consistent.
;-----------------------------------------------------------
now let's get moving:
|#
(setf (time-elapsed *s2*) 1)
#|
...and observe:
0> echo time-elapsed :new 1 :old 0 :oldp t
echo distance fallen: 16 feet
behind the scenes:
- the slot value time-elapsed got changed from 0 to 1
- the time-elapsed echo was called
- dependents on time-elapsed (here just distance) were recalculated
- go to the first step, this time for the distance slot
;-----------------------------------------------------------
to see some optimizations at work, set the cell time-elapsed to
the same value it already has:
|#
(setf (time-elapsed *s2*) 1)
#| observe:
nothing, since the slot-value did not in fact change.
;-----------------------------------------------------------
to test the enforcement of the cell stricture against
modifying cells holding naked values:
|#
(handler-case
(setf (accel *s2*) 10)
(t (error) (trc "error is" error)
error))
#| observe:
c-setting-debug > constant accel in stone may not be altered..init to (c-in nil)
0> error is #<simple-error @ #x210925f2>
;-----------------------------------------------------------
nor may ruled cells be modified arbitrarily:
|#
(handler-case
(setf (distance *s2*) 42)
(t (error) (trc "error is" error)
error))
#| observe:
c-setting-debug > ruled distance in stone may not be setf'ed
0> error is #<simple-error @ #x2123e392>
;-----------------------------------------------------------
aside from c?, cv, and def-c-output, another thing you will see
in cello code is how complex views are constructed using
the family class and its slot kids. every model-object has a
parent slot, which gets used along with a family's kids slot to
form simple trees navigable up and down.
model-objects also have slots for md-name and md-value (don't
worry camelcase-haters, that is a declining feature of my code).
md-name lets the family trees we build be treated as namespaces.
md-value just turns out to be very handy for a lot of things. for
example, a check-box instance needs some place to indicate its
boolean state.
now let's see family in action, using code from the handbook of
silly examples. all i want to get across is that a lot happens
when one changes the kids slot. it happens automatically, and
it happens transparently, following the dataflow implicit in the
rules we write, and the side-effects we specify via echo functions.
the silly example below just shows the summer (that which sums) getting
a new md-value as the kids change, along with some echo output. in real-world
applications, where kids represent gui elements often dependent on
each other, vastly more can transpire before a simple push into a kids
slot has run its course.
evaluate:
|#
(defmodel summer (family)
()
(:default-initargs
:kids (c-in nil) ;; or we cannot add any addend kids later
:md-value (c? (reduce #'+ (kids self)
:initial-value 0
:key #'md-value))))
(def-c-output .md-value ((self summer))
(trc "the sum of the values of the kids is" new-value))
(def-c-output .kids ((self summer))
(trc "the values of the kids are" (mapcar #'md-value new-value)))
;-----------------------------------------------------------
; now just evaluate each of the following forms one by one,
; checking results after each to see what is going on
;
(defparameter *f1* (make-instance 'summer))
#|
observe:
0> the sum of the values of the kids is 0
0> the values of the kids are nil
;----------------------------------------------------------|#
(push (make-instance 'model :md-value 1) (kids *f1*))
#| observe:
0> the values of the kids are (1)
0> the sum of the values of the kids is 1
;----------------------------------------------------------|#
(push (make-instance 'model :md-value 2) (kids *f1*))
#| observe:
0> the values of the kids are (2 1)
0> the sum of the values of the kids is 3
;----------------------------------------------------------|#
(setf (kids *f1*) nil)
#| observe:
0> the values of the kids are nil
0> the sum of the values of the kids is 0
now before closing, it occurs to me you'll need a little
[33 lines skipped]
--- /project/cells-gtk/cvsroot/cells/doc/cell-doc.lisp 2006/06/07 16:28:58 NONE
+++ /project/cells-gtk/cvsroot/cells/doc/cell-doc.lisp 2006/06/07 16:28:58 1.1
[215 lines skipped]
--- /project/cells-gtk/cvsroot/cells/doc/hw.lisp 2006/06/07 16:28:58 NONE
+++ /project/cells-gtk/cvsroot/cells/doc/hw.lisp 2006/06/07 16:28:58 1.1
[287 lines skipped]
More information about the Cells-gtk-cvs
mailing list