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? (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).
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?
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).
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).
Thanks in advance for all your comments.
Mike
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.
- 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?
- 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))))
- 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?
- 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
"Kenny" == Kenny Tilton ktilton@nyc.rr.com writes:
Kenny> Mike J. Bell wrote:
[...snip...]
Kenny> Cells have always grown in response to application Kenny> requirements, so if someone comes up with a good example I Kenny> will gladly look at implementing cycles in Cells. The Kenny> question is, how can a = f(b) and b=f(a) ever be computed? Kenny> In your example, there is no real cycle, because you are Kenny> saying: "dollars is a function of yen /when changes/, and Kenny> yen is a function of dollar /when dollar changes/." Only Kenny> one can be changing at a time, so there is no cycle as the Kenny> problem is conceived.
Kenny> But how is an instance initialized? I can supply conversion Kenny> rules for dollar-balance and yen-balance, but how do I get Kenny> the intial balance in there? I would need to specify a Kenny> starting value, but for which one? Both? What if I specify Kenny> 1 for both? That conversion would be wrong, the dollar is Kenny> (for now) worth more than 1 yen.
Kenny> Conceivably the answer is to let the programmer worry about Kenny> it. I could specify a value (and the conversion rule) for Kenny> dollar-balance and specify "unbound" and a rule for all Kenny> other currencies. Then if the others get accessed, the Kenny> system just calculates as usual. If all values are unbound Kenny> a runtime error results. If the programmer specifies Kenny> inconsistent initial values they get a wrong result (which Kenny> we might be able to detect at some point of runtime) until Kenny> some SETF perturbs the model and the formulas kick in to Kenny> make things consistent.
Kenny> Thoughts on this?
IIRC, Garnet's KR has a mechanism (one might call it "a cheap hack") to solve the problem of cyclic dependencies, which was to allow you to "plonk" an initial value into a slot that would be used to kick the whole mess off.
Best, Robert
rpgoldman@real-time.com wrote:
"Kenny" == Kenny Tilton ktilton@nyc.rr.com writes:
Kenny> Mike J. Bell wrote:
[...snip...]
Kenny> Cells have always grown in response to application Kenny> requirements, so if someone comes up with a good example I Kenny> will gladly look at implementing cycles in Cells. The Kenny> question is, how can a = f(b) and b=f(a) ever be computed? Kenny> In your example, there is no real cycle, because you are Kenny> saying: "dollars is a function of yen /when changes/, and Kenny> yen is a function of dollar /when dollar changes/." Only Kenny> one can be changing at a time, so there is no cycle as the Kenny> problem is conceived.
Kenny> But how is an instance initialized? I can supply conversion Kenny> rules for dollar-balance and yen-balance, but how do I get Kenny> the intial balance in there? I would need to specify a Kenny> starting value, but for which one? Both? What if I specify Kenny> 1 for both? That conversion would be wrong, the dollar is Kenny> (for now) worth more than 1 yen.
Kenny> Conceivably the answer is to let the programmer worry about Kenny> it. I could specify a value (and the conversion rule) for Kenny> dollar-balance and specify "unbound" and a rule for all Kenny> other currencies. Then if the others get accessed, the Kenny> system just calculates as usual. If all values are unbound Kenny> a runtime error results. If the programmer specifies Kenny> inconsistent initial values they get a wrong result (which Kenny> we might be able to detect at some point of runtime) until Kenny> some SETF perturbs the model and the formulas kick in to Kenny> make things consistent.
Kenny> Thoughts on this?
IIRC, Garnet's KR has a mechanism (one might call it "a cheap hack") to solve the problem of cyclic dependencies, which was to allow you to "plonk" an initial value into a slot that would be used to kick the whole mess off.
Yes. I was frankly horrified by all the backdoor hacks KR allowed. I must be getting old and stodgy.
:)
kenny