OK, here is the promised write-up, with a scary proposed fix. What do you all think?<br><br>the code, btw, ships in broken form: the text-box will not have a complete record. Move the featuring around in the text-box rule to make an awkward fix tantamount to the fix I have in mind to for the Cells engine.
<br><br>I will follow up separately with a discussion of the proposed fix.<br><br>kenny<br><br>(defpackage #:tu-some-ephemeral-uhoh (:use :cl :utils-kt :cells :tu-cells))<br>(in-package #:tu-some-ephemeral-uhoh)<br><br>#|
<br><br>SOME over a list of ephemerals will not establish useful dependencies. Actually, I think there<br>is just a larger problem with the current implementation of ephemerals. More below.<br>The short-term fix is to force iteration over all ephemerals, then return the first found.
<br>This is because ephemerals do not change to NIL visibly to propagation --<br>it is a silent reset done by internals. Details follow:<br><br>Normally Cells and SOME get along fine. The spirit of SOME is to find just the first non-nil
<br>result in a list, returned by its predicate argument. To stay current with such an expression<br>after non-nil value V is returned by some instance F (the first instance in the list to<br>return a non-nil value), the rule should run if:
<br><br>    an instance appearing earlier in the list would now return a non-nil value<br>    the predicate would return a different value V2 if applied to the same instance F<br>    in the special case where the new value returned by F would be nil, we want a new search
<br>       down the list until (possibly) some other instance F2 returns a value.<br><br>Well, as I said, normally that works fines, assuming the predicate's return value is<br>affected only by Cells. (This, btw, is a good example of why it is hard to be "a little
<br>bit Cells".) Dependencies will exist on the population of the list of instances, and, for all <br>instances up to F, dependencies will exist on all Cells going into the predicate's derivation<br>of a value. <br><br>
If you stare at the three cases above, you will see that they all work. Note also that they work <br>even though no dependencies exist from applying the predicate to instances /after/ F. That is because<br>they do not matter until F decides to return NIL, and but that change will trigger the rule to
<br>run again and sail past F to (possibly) some new F2, establishing dependencies all along the way.<br><br>And now the problem. Suppose the predicate simply reads and ephemeral slot. Some instance F takes <br>on a value V for that slot and the rule runs. The value V gets returned, so the rule is not dependent
<br>on any instance after F. Fine. But when this propagation completes, because the slot is ephemeral, it <br>reverts to nil without propagating, which is exactly when above the rule ran again, sailed past F<br>and established dependencies of instances farther down the list, ready for someone to turn non-nil.
<br><br>When some F2 farther down the line /does/ change to return a non-nil, or even if it was ready to return<br>a non-nil when F did so it never got asked (SOME just wants the first), the rule having no dependency<br>past F will not run. 
<br><br>The solution? Do not use SOME in conjunction with ephemerals. Iterate over the whole list to <br>establish dependencies and then take the first result found. if this is to inefficient, have an observer<br>on the ephemeral propagate state change via deferred SETF.
<br><br>Meanwhile, i will be looking for a fix that makes ephemerals more transparent. The only thing that springs<br>to mind (this is a preview) is re-running the rule after resetting the ephemeral just to establish <br>
the dependencies. Which is sick because the code will branch differently -- but that is the idea!<br><br>Like I said, sick. :)<br><br>|#<br>(defparameter *newline* (princ-to-string #\Newline))<br><br>(defmodel cells-chat (family) ;; kids slot can be partcipants
<br>  ((text-box :initarg :text-box :accessor text-box<br>     :initform (let (last-chatters)<br>                 (c? (let ((latest-speech<br>                            ;; broken...<br>                            (some (lambda (p)
<br>                                    (when (speech p)<br>                                      ;(print (speech p))<br>                                      (cons p (speech p)))) ;; no dependencies after this<br>                              (^kids))
<br>                            ;; fixed (always hit all speeches to estab dependencies)<br>                            #+(or)<br>                            (loop with result<br>                                for p in (^kids)
<br>                                when (and (speech p) (not result)) ;; this order or same bug<br>                                  do (setf result (cons p (speech p)))<br>                                  finally (return result))
<br>                            <br>                             )<br>                           (new-chatters (set-difference (^kids) last-chatters))<br>                           (lost-chatters (set-difference last-chatters (^kids))))
<br>                     (prog1<br>                         (cond<br>                          (latest-speech<br>                           (destructuring-bind (p . s) latest-speech<br>                             (concatenate 'string
<br>                               (or .cache "")<br>                               (username p) ": " s *newline*)))<br>                          (lost-chatters (concatenate 'string<br>                                           (or .cache "")
<br>                                           (format nil "~a has/have left the chat~a"<br>                                             (mapcar 'username lost-chatters) *newline*)))<br>                          (new-chatters (concatenate 'string
<br>                                          (or .cache "")<br>                                          (format nil "~a has/have joined the chat~a"<br>                                            (mapcar 'username new-chatters) *newline*)))
<br>                          (t .cache))<br>                       (setf last-chatters (^kids)))))))))<br><br>(defmodel chatter (model)<br>  ((username :cell nil :accessor username :initarg :username<br>            :initform (error "chatter needs a `username'."))
<br>   (speech :cell :ephemeral :initform (c-in nil)<br>     :initarg :speech :accessor speech)))<br><br>(defobserver text-box ((chat cells-chat) new-value old-value)<br>  #+confusingoutput (when new-value<br>    (format t "~&--------------(text-box-of ~A)------------------:~&'~A'~%" chat new-value)))
<br><br>(defun tu-cells::tu-some-ephemeral-uhoh ()<br>  (cells-reset)<br>  (let* ((chat (make-instance 'cells-chat))<br>         (lars (make-instance 'chatter<br>                 :fm-parent chat<br>                 :username "Lars")))
<br>    (push lars (kids chat))<br>    (setf (speech lars) "Cells are different.")<br>    (push (make-instance 'chatter<br>            :fm-parent chat<br>            :username "Kenny") (kids chat))<br>
    (setf (speech lars) "Hi, kenny")<br>    ;<br>    ; this next state change causes the text-box to lose its dependency on (speech lars)...<br>    ;<br>    (setf (speech (car (kids chat))) "Hi, Lars. That's for sure. Takes a while to adjust.")
<br>    ;<br>    ; this next state change will not be propagated...<br>    ;<br>    (setf (speech lars) "OK, I'll keep plugging")<br>    (depart-chat lars)<br>    (print (text-box chat))))<br><br><br>(defun depart-chat (chatter)
<br>  (print `(departing ,chatter))<br>  (setf (kids (fm-parent chatter)) (remove chatter (kids (fm-parent chatter)))))