Mike J. Bell wrote:
>On Sat, 05 Mar 2005 10:24:17 -0500, Kenny Tilton <ktilton(a)nyc.rr.com> wrote:
>
>
>>Mike J. Bell wrote:
>>
>>
>>> (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.
>>
>>
>
>If you were to model calculations and slots *separately*, i.e. have
>data objects to hold the values you're worried about, and function
>objects that respond to changes in these data objects by changing
>other data objects, then you can get into update cycles.
>
>For instance, given data objects A and B, and function objects F1 and
>F2, if F1 responds to changes in A and then sets B, and F2 responds to
>changes in B and then sets A, you could cycle infinitely without
>reaching steady state. A system designed with F1 and F2 is designed
>incorrectly. In the paradigm I'm currently using, there's a special
>type of function that maintains changes between A and B so as to
>eliminate this infinite cycle. It's actually quite simple, and you
>elude to the solution above: this special function is really just a
>tuple of the original F1 and F2, with the added semantic that the
>calculation code knows that if A changed, run function F1; if B
>changed, run function F2; if both changed, signal an error.
>
Yes. It is a cycle only if the system is ignorant of the full spec.
By the way, with Cells it is possible to declare a formula as "cyclic".
But this is a bit of a hack. The way it works for your scenario is this:
Define an output method on slot A which sets slot B.
Define an output method on slot B which sets slot A.
During propagation, keep track of who is being set during any one data
"pulse".
If I discover, say, that A is already being set when we get another
attempt to set A:
If A has been defined to be cyclic, simply abandon the subsequent
set operation
else produce a runtime error
The bad news is that we lose the declarative thing, because the rule for
each calculation is now in an output method (which is furthermore only
slot-specific, where Cell rules can be instance-specific). Hmmm, maybe I
should go back to the old scheme. Well, I always wait for an issue to
get truly troublesome before fixing it, just to have as clear an idea as
possible of the problem I am fixing.
>
>(This is starting to expose some of the differences between what I'm
>using and Cells...in my system, there are distinct phases of updates
>apparently like in your Cells II).
>
>
>
>>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?
>>
>>
>
>In the system I currently use, the programmer worries about it, and is
>only allowed to set one initial value. The system recognizes that the
>other slot has no value, that it *could* have a value due to this
>two-way conversion, and runs the appropriate function to find what it
>is. If the programmer sets an initial value for both slots, even if
>they are consistent with the function, the system signals an error.
>
Cool.
>
>
>
>>>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?
>>
>>
>
>I think I understand this...the reason you set the ephemeral slot back
>to nil is so that when its value changes again in the future, any
>slots dependent on it will definitely fire.
>
Not exactly. I have delta slots for that. They are understood to express
a change, so each time I set a slot to any value other than a
user-definable identity value, propagation takes place.
Besides, events are distinct Lisp objects (not EQL) so a new event may
be EQUAL if you will, but it will be a new Lisp object and cause the
rule to fire.
The reason I have ephemerals is, simply, that events are ephemeral!
Consider this partial rule:
(c? (let ((evt (^mouse-down (window self))))
(when (and evt (rect-contains (^my-rect self) (mouse-pos
evt)))....
Now this sequence occurs:
a mouse-down in the window, in or out of my-rect, no matter.
a mouse-up occurs
my-rect changes in response to any other event besides mouse-down
Well, the rule fires, because my-rect changed. The mouse button is no
longer down, but we still have an old event sitting in the mouse-down slot.
We could have a nice WITH-EPHEMERAL-SETF (place value) macro, but that
would end with (setf place nil), which would kick off propagation (hmm,
not the end of the world, and it /would/ clear dependencies. hmmm...) or
we could end with (setf (slot-value <self> <place>) nil), but then that
is what ephemrals do.
Gosh, maybe :ephemeral was a mistake? If I do propagate via (setf ,place
nil), then the rule fires and the dependency on MY-RECT gets cleared, as
it should. And then I can eliminate the whole complexification of the
ephemeral option. The downside is that ephemerality is a property of the
slot (anything known to hold an OS event, for example), so if I
eliminate that I then have to worry about using WITH-EPHEMRAL-SETF
consistently.
Maybe the right thing is for Cells internals to handle ephemerality by
propagating the reset instead of doing it silently.
>In the system I use, since the data is separate from the functions,
>it's not *really* object-oriented. And we end up with quite a few of
>these data that represent "something happening." In our system, the
>identity of the data (i.e. which slot in which object) conveys all the
>information that is necessary to know when a button is pressed.
>Notice this is at an application level, not the GUI level. I'm sure
>that in the GUI internals, there's a lot of complicated information
>that goes into a button press...but at the system level, when you want
>to know, "hey, this button was pressed!"...that's essentially
>stateless (excepting, like I said, identity).
>
>There are also frequent other times this comes into effect, and it all
>falls into a command-pattern type of deal. "I have collected data X,
>Y and Z...now DO this." The DO is essentially stateless.
>
>I would imagine since we have implicit transactions and automated
>rollback capabilities (and all that implies...meaning phased execution
>cycles to propogate the "waves" of data modifications) that this
>"staging" of values to perform a calculation is 100% the fault of the
>imperative event model. If Cells is really completely declarative
>(like Haskell), then it's going to take me a while to wrap my brain
>into that model to see how they compare.
>
>
>
>>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.
>>
>>
>
>Interesting. That's definitely different from what I'm using. Often
>times data updates hundreds of times in each transaction before
>settling on a new value.
>
I got away with that for years (with many different kinds of
applications) before it broke down (while doing a RoboCup soccer
simulator of all things).
kt