[cells-devel] Re: chat-server using cells?

Ken Tilton kentilton at gmail.com
Tue Jun 13 03:36:19 UTC 2006


OK, here is the promised write-up, with a scary proposed fix. What do you
all think?

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.

I will follow up separately with a discussion of the proposed fix.

kenny

(defpackage #:tu-some-ephemeral-uhoh (:use :cl :utils-kt :cells :tu-cells))
(in-package #:tu-some-ephemeral-uhoh)

#|

SOME over a list of ephemerals will not establish useful dependencies.
Actually, I think there
is just a larger problem with the current implementation of ephemerals. More
below.
The short-term fix is to force iteration over all ephemerals, then return
the first found.
This is because ephemerals do not change to NIL visibly to propagation --
it is a silent reset done by internals. Details follow:

Normally Cells and SOME get along fine. The spirit of SOME is to find just
the first non-nil
result in a list, returned by its predicate argument. To stay current with
such an expression
after non-nil value V is returned by some instance F (the first instance in
the list to
return a non-nil value), the rule should run if:

    an instance appearing earlier in the list would now return a non-nil
value
    the predicate would return a different value V2 if applied to the same
instance F
    in the special case where the new value returned by F would be nil, we
want a new search
       down the list until (possibly) some other instance F2 returns a
value.

Well, as I said, normally that works fines, assuming the predicate's return
value is
affected only by Cells. (This, btw, is a good example of why it is hard to
be "a little
bit Cells".) Dependencies will exist on the population of the list of
instances, and, for all
instances up to F, dependencies will exist on all Cells going into the
predicate's derivation
of a value.

If you stare at the three cases above, you will see that they all work. Note
also that they work
even though no dependencies exist from applying the predicate to instances
/after/ F. That is because
they do not matter until F decides to return NIL, and but that change will
trigger the rule to
run again and sail past F to (possibly) some new F2, establishing
dependencies all along the way.

And now the problem. Suppose the predicate simply reads and ephemeral slot.
Some instance F takes
on a value V for that slot and the rule runs. The value V gets returned, so
the rule is not dependent
on any instance after F. Fine. But when this propagation completes, because
the slot is ephemeral, it
reverts to nil without propagating, which is exactly when above the rule ran
again, sailed past F
and established dependencies of instances farther down the list, ready for
someone to turn non-nil.

When some F2 farther down the line /does/ change to return a non-nil, or
even if it was ready to return
a non-nil when F did so it never got asked (SOME just wants the first), the
rule having no dependency
past F will not run.

The solution? Do not use SOME in conjunction with ephemerals. Iterate over
the whole list to
establish dependencies and then take the first result found. if this is to
inefficient, have an observer
on the ephemeral propagate state change via deferred SETF.

Meanwhile, i will be looking for a fix that makes ephemerals more
transparent. The only thing that springs
to mind (this is a preview) is re-running the rule after resetting the
ephemeral just to establish
the dependencies. Which is sick because the code will branch differently --
but that is the idea!

Like I said, sick. :)

|#
(defparameter *newline* (princ-to-string #\Newline))

(defmodel cells-chat (family) ;; kids slot can be partcipants
  ((text-box :initarg :text-box :accessor text-box
     :initform (let (last-chatters)
                 (c? (let ((latest-speech
                            ;; broken...
                            (some (lambda (p)
                                    (when (speech p)
                                      ;(print (speech p))
                                      (cons p (speech p)))) ;; no
dependencies after this
                              (^kids))
                            ;; fixed (always hit all speeches to estab
dependencies)
                            #+(or)
                            (loop with result
                                for p in (^kids)
                                when (and (speech p) (not result)) ;; this
order or same bug
                                  do (setf result (cons p (speech p)))
                                  finally (return result))

                             )
                           (new-chatters (set-difference (^kids)
last-chatters))
                           (lost-chatters (set-difference last-chatters
(^kids))))
                     (prog1
                         (cond
                          (latest-speech
                           (destructuring-bind (p . s) latest-speech
                             (concatenate 'string
                               (or .cache "")
                               (username p) ": " s *newline*)))
                          (lost-chatters (concatenate 'string
                                           (or .cache "")
                                           (format nil "~a has/have left the
chat~a"
                                             (mapcar 'username
lost-chatters) *newline*)))
                          (new-chatters (concatenate 'string
                                          (or .cache "")
                                          (format nil "~a has/have joined
the chat~a"
                                            (mapcar 'username new-chatters)
*newline*)))
                          (t .cache))
                       (setf last-chatters (^kids)))))))))

(defmodel chatter (model)
  ((username :cell nil :accessor username :initarg :username
            :initform (error "chatter needs a `username'."))
   (speech :cell :ephemeral :initform (c-in nil)
     :initarg :speech :accessor speech)))

(defobserver text-box ((chat cells-chat) new-value old-value)
  #+confusingoutput (when new-value
    (format t "~&--------------(text-box-of ~A)------------------:~&'~A'~%"
chat new-value)))

(defun tu-cells::tu-some-ephemeral-uhoh ()
  (cells-reset)
  (let* ((chat (make-instance 'cells-chat))
         (lars (make-instance 'chatter
                 :fm-parent chat
                 :username "Lars")))
    (push lars (kids chat))
    (setf (speech lars) "Cells are different.")
    (push (make-instance 'chatter
            :fm-parent chat
            :username "Kenny") (kids chat))
    (setf (speech lars) "Hi, kenny")
    ;
    ; this next state change causes the text-box to lose its dependency on
(speech lars)...
    ;
    (setf (speech (car (kids chat))) "Hi, Lars. That's for sure. Takes a
while to adjust.")
    ;
    ; this next state change will not be propagated...
    ;
    (setf (speech lars) "OK, I'll keep plugging")
    (depart-chat lars)
    (print (text-box chat))))


(defun depart-chat (chatter)
  (print `(departing ,chatter))
  (setf (kids (fm-parent chatter)) (remove chatter (kids (fm-parent
chatter)))))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mailman.common-lisp.net/pipermail/cells-devel/attachments/20060612/358da6d9/attachment.html>


More information about the cells-devel mailing list