[cells-gtk-devel] Re: [cells-devel] Newbie questions

Kenny Tilton ktilton at nyc.rr.com
Sat Mar 5 15:24:17 UTC 2005



Mike J. Bell wrote:

>I suppose some of this might be in a FAQ or something, but I'm
>somewhat lazy and I've got some questions burning.
>
>I also apologize for A) not knowing CL very well B) not knowing very
>much about CLOS at all (except what it stands for).  Please pardon any
>strange or misused terminology.
>
>That said, I've got some questions about how Cells works.
>
>1) Synchronicity, or the currency conversion problem:  Let's say I
>define a slot in an object that represents the amount of US Dollars I
>have in a bank account.  Let's further say that in my app I also want
>to represent the amount of Japanese Yen in my account.  If someone
>"sets" the amount of dollars, I want the yen slot to be updated.  If
>someone sets the yen, I want the dollars to be updated.  Is there a
>mechanism to create such co-dependent cells?
>
I did a foreign exchange system once, so I would say always update the 
dollars. <g>

But to answer your question, the way I handle things like scroll bars 
(the thumb can be used to move the text, but scrolling thru the text 
with the arrow keys moves the thumb) is to have DEF-C-OUTPUT methods on 
both the text offset and (really) the scrollbar ratio, with each one 
setf-ing the other, meaning both get initialized to (c-input 0).

I used to have a more elaborate scheme in Cells which handled this sort 
of thing by swapping in a different pair of rules when the user 
moused-down on the thumb, but I decided dual outputs, while losing the 
declarative thing, were better because it let me eliminate the whole 
dynamic rule-swapping section of Cells.

>  (Obviously this is
>addressing the update cycle problem, and at the shortest path level: 
>A depends on B depends on A (length 1), which forms a very useful
>group of calculations).
>
Cells have always grown in response to application requirements, so if 
someone comes up with a good example I will gladly look at implementing 
cycles in Cells. The question is, how can  a = f(b) and b=f(a) ever be 
computed? In your example, there is no real cycle, because you are 
saying: "dollars is a function of yen /when changes/, and yen is a 
function of dollar /when dollar changes/." Only one can be changing at a 
time, so there is no cycle as the problem is conceived.

But how is an instance initialized? I can supply conversion rules for 
dollar-balance and yen-balance, but how do I get the intial balance in 
there? I would need to specify a starting value, but for which one? 
Both? What if I specify 1 for both? That conversion would be wrong, the 
dollar is (for now) worth more than 1 yen.

Conceivably the answer is to let the programmer worry about it. I could 
specify a value (and the conversion rule) for dollar-balance and specify 
"unbound" and a rule for all other currencies. Then if the others get 
accessed, the system just calculates as usual. If all values are unbound 
a runtime error results. If the programmer specifies inconsistent 
initial values they get a wrong result (which we might be able to detect 
at some point of runtime) until some SETF perturbs the model and the 
formulas kick in to make things consistent.

Thoughts on this?

>
>2) Equals:  It looks like, from reading 01-Cell-basics.lisp, that
>setting a slot to an "equivalent" object does not cause all its
>dependencies to fire again (in order to save substantial useless
>recalculations).  What does "equivalent" mean?  Can I install my own
>equivalency operator, per slot (or per class would be fine) so that
>Cells knows when computations should be rerun?
>
Yes. There is an :unchanged-if option (default EQL (supplied by Cells 
internals)) on slot definitions in DEFMODEL:

Example from Cello:
(defmodel image (ogl-node model)
   ((clipped :cell nil :initarg :clipped :initform nil :reader clipped)
    (inset :initarg :inset
      :unchanged-if 'v2=
      :initform (mkv2 0 0) :reader inset)
     ....etc....

[Actually, I just noticed that I also had ":cell nil" on that! "inset" 
was not a Cell for a lonnnggg time, and I think when I finally wanted to 
make it a Cell I was smart enough to specify unchanged-if, but dumb 
enough to leave behind :cell nil. Looks like I never tested... perhaps 
before I got done I decided against playing with inset? Anyway, that is 
neither there nor there.]

(defstruct v2
  (h 0 )
  (v 0 )
  )

(defun v2= (a b)
  (and a b
    (= (v2-h a)(v2-h b))
    (= (v2-v a)(v2-v b))))


>
>3) Lack of values, or triggers:  It's easy to come up with typed slots
>that participate in calculations.  However, what about untyped events?
> For instance, a press of a button conveys no useful state
>information.  Is it possible in Cells to make slots dependent upon the
>"change" of a stateless slot?  (This sort of ties back into the equals
>business...I understand you could implement this sort of functionality
>by setting a slot to, for example, a gensym uninterned symbol, so that
>there is no useful value and it's sure to cause everything to
>recalculate by any useful definition of equals, but this solution
>is...well, kind of ugly.  It would be nice to be able to define the
>recalculation or "firing" semantics of a slot, which is a superset of
>the "equals" issue above).
>
I am afraid I do not follow you. My GUIs all manage buttons and other 
standard GUI components via Cells. A button has a "click event" slot 
(because clicking is surprisingly complicated) which Cello 
mouse-handling SETFs (so it is initialized to (c-input nil). Anyway, I 
am not answering your question... could you explain again what you mean 
by a stateless slot?

By the way, in case this helps, as a steady-state, declarative approach, 
Cells do make handling events tricky. I came up with the :ephemeral 
option to the :cell option of a slot definition:

(defmodel window ....
   (mouse-up-evt :cell :ephemeral :initarg :mouse-up-evt :initform (c-in 
nil) :accessor mouse-up-evt)
  .....

The way it works is this: when I SETF such a slot, the engine takes care 
of all propagation and then silently (not via any accessor) resets the 
slot value to nil.

Not sure, but I think the internals yell at you if you try to initialize 
to anything other than nil, such as (c-in 42).

Close?

>
>4) Transactions:  Let's say you have 50 or 60 slots that are connected
>in a complicated graph of calculation.  A change to one slot (let's
>call this "initiation") causes all of these guys to start running
>until finally the system reaches a steady state and no more changes
>occur.  We could conceptualize this initiation, subsequent
>calculations and then collapse into steady state as a single
>"transaction."
>
>Now, imagine that at the second-to-last calculation of this
>propagation of values, the system enters a state of inconsistency.  In
>other words, the initiation of that one state change way back in the
>beginning is actually invalid.  However, we don't know that (given
>that this is a nice event-driven framework) until step 52, and we've
>got 40 slots that are all now different than they were, and *wrong*. 
>Ideally, it would be nice to have mechanism that A) notifies the
>programmer, similar to a "catch" construct, so that s/he can note it
>in an appropriate fashion, and B) restores all the slots in the system
>to the last known good steady state; i.e., what they were before the
>initiation occured.
>
>Does Cells have a transaction model similar to this?  If so, can
>someone explain it?  Are there separate phases of execution in the
>transaction?  Are there separate "guards" that are responsible for
>determining the validity of system state?  Is there a way to specify
>that slots get updated only once the system state becomes steady; i.e.
>they don't participate in the middle of the transaction?  (These
>beasts have quite a few constraining properties, I realize, but they
>could (are) be very useful to reduce the amount of "spinning" the
>recalculation has to do).
>
No, there is no such rollback mechanism in Cells, but it is not a bad 
idea. If you check out the function finish-business, you might be able 
to make out the flow:

- first, recalculate all "users" (direct dependents) of the original 
changed cell, recursively propagating to users of users as necessary.

- second, invoke any output methods defined on the changed cells

- third, invoke SETF's of cells deferred during outputs (that is indeed 
legal)

note that the processing of each SETF is handled by executing those same 
steps. Anyway, rollback would take a little work since there is no 
dynamic scope encompassing all the state changes -- then we could just 
restore the slot value in unwind-protect -- but it would be feasible.

As for other questions, the latest revision of Cells (I call it Cells 
II) guarantees the following (I really should write this up!):

- during propagation, each cell calculates only once
- no slot accessor returns a "stale" value, by which I mean a value 
which has yet to be re-calculated after some dependency direct or 
indirect has changed.

kt

-- 
Cells? Cello? Cells-Gtk?: http://www.common-lisp.net/project/cells/
Why Lisp? http://lisp.tech.coop/RtL%20Highlight%20Film

"Doctor, I wrestled with reality for forty years, and I am happy to state that I finally won out over it." -- Elwood P. Dowd





More information about the cells-gtk-devel mailing list