[cells-cvs] CVS kennysarc2

ktilton ktilton at common-lisp.net
Thu Feb 14 12:30:22 UTC 2008


Update of /project/cells/cvsroot/kennysarc2
In directory clnet:/tmp/cvs-serv15889

Added Files:
	cells-1.arc 
Log Message:
Trying to get warning-free build


--- /project/cells/cvsroot/kennysarc2/cells-1.arc	2008/02/14 12:30:22	NONE
+++ /project/cells/cvsroot/kennysarc2/cells-1.arc	2008/02/14 12:30:22	1.1
;;; Utilities
;;; ---------

(def prt args
  (apply prs args)
  (prn))

(mac prun (banner . forms)
  `(do (prn ,banner)
       (prn '(do , at forms))
     (prn)
     , at forms))

(prun "
 Cells
 -----
 Let's start with the moral equivalent of a C++ member function,
 or at least I think that's what they call it. I mean what looks
 like a stored data member or slot of an attribute of an object
 that is in fact implemented as a function of that object.

 An example would be having a rectangle object with data members
 where one could store length and width and then have an area
 attribute implemented (in C++) as:

   area = this.length * this.width;

 Aside from saving a little memory, one gets a guarantee that the
 area will always be consistent with the length and width, which
 is not the case if one is writing code that says oh gosh I just
 changed the length I better go change the area.

 As our 'application pushing down on the core' we'll use my
 favorite, a boiler.
"

  (= b* (obj outside-temp 72
         on? [< _!outside-temp 50])))

(prun "
 No, the outside temp is not an attribute of a boiler, we're just
 keeping things in one table as a convenience until we get
 the ball rolling, later on we'll deal with multiple objects.

 That anonymous function above boils down to:

   If the outside temp is less than 50,
     then turn on the boiler,
     otherwise turn it off. 

First, let's see if the rule works (not a big accomplishment)
"
  (prt 'boiler b*!outside-temp (if (b*!on? b*) 'on 'off)))

(prun "

Good, now change temp to 32 and see if the boiler comes on:
"

  (= b*!outside-temp 32)
  (prt 'boiler b*!outside-temp (if (b*!on? b*) 'on 'off)))

(prun "

-> boiler 32 on

Super. Now let's hide the fact that on? is a function
behind a reader function:
"
  (def on? (i) (i!on? i)))

;;; and ease inspection:

(def pr-boiler (b)
  (prt 'boiler 'temp b*!outside-temp (if (on? b) 'on 'off)))

(prun "
Test new slot reader, setting temp high enough this time
so that the boiler should go off:
"
  (= b*!outside-temp 80)
  (pr-boiler b*))

(prn "

 Super. But we want more flexibility than having an attribute
 always defined by a function. Maybe we just want to store nil or t in on?
 and maintain it as usual, via assignment. Now on? can no longer be 
 assumed to be a function. Fortunately we already
 have it behind a reader in our burgeoning little OO system, so we just
 need to enhance that (and get a redefinition warning):
")

(def on? (i)
  (awhen i!on?
    (if (isa it 'fn)
        (it i)
      it)))

(prun "
Can a slot of a different boiler be maintained by other code?
Start with a hard-coded NIL for on?...
"

  (= b* (obj outside-temp -10 on? nil))
  (pr-boiler b*))


(prun "
Now assign t to on?
"
  (= b*!on? t) ; We'll hide the assignment implementation later.
  (pr-boiler b*))

(prun "
Super. 

We will want all our attributes to work this way, so we
may as well generalize the on? behavior now:
"

  (def slot-value (i slot-name) ;; i is like self ala Smalltalk
    (awhen i.slot-name
      (if (isa it 'fn)
          (it i)
        it))))

(mac defslot (name)
  `(def ,name (i) (slot-value i ',name)))

(defslot outside-temp)
(defslot on?)
(defslot inside-temp) ;; Let's start elaborating the model

(def pr-boiler (i)
  (prt 'boiler
    'outside-temp (outside-temp i)
    (if (on? i) 'on 'off)
    'inside-temp (inside-temp i)))

(prun "
And test:
"
  (= b* (obj outside-temp 20
         on? nil
         inside-temp [if (on? _)
                         72
                       _!outside-temp]))
  (pr-boiler b*))

(prun "
Super. Now let's bring back the automatic boiler:
"
  (= b*!on? [< _!outside-temp 50]))

(prun "
Step temperature up from freezing to torrid.
"
  (loop (= b*!outside-temp 30) (< b*!outside-temp 100) (= b*!outside-temp (+ b*!outside-temp 10))
        (pr-boiler b*)))

;;; Super. But we need an air conditioner. And let's get more realistic about the model

(= outside* (obj temp 20))
(defslot temp)

(= furnace* (obj on? [< (temp outside*) 50]))
(= ac* (obj on? [> (temp outside*) 75])) ;; air conditioner
(= inside* [if (on? furnace*) 72
             (on? ac*) 68
             (temp outside*)])

(def dumpworld ()
  (prt "outside" (temp outside*))
  (prt "furnace" (if (on? furnace*) 'on 'off))
  (prt "a/c" (if (on? ac*) 'on 'off))
  (prt "inside" (temp inside*)))

;;;(prun "
;;;Step temperature up from freezing to torrid, but with an air-conditioner
;;;"
;;;  (loop (= outside*!temp 30) (< outside*!temp 100) (= outside*!temp (+ outside*!temp 10))
;;;        (prn)
;;;        (dumpworld)))

;;; Nice. We have built a working model that runs by itself given simple declarative
;;; rules, meaning we state the rules and an engine sees to it that the model
;;; runs. But we have a problem. Let's add a debug option to our slots:

(def slot-value (i slot-name (o debug)) ;; i is like self ala Smalltalk
    (awhen i.slot-name
      (if (isa it 'fn)
          (do
              (when debug (prt "Running the rule for slot" slot-name))
              (let result (it i)
                (when debug (prt "...slot" slot-name "is" result))
                result))
        it)))

(mac defslot (name (o debug))
  `(def ,name (i) (slot-value i ',name ,debug)))

(defslot on? t)

;;;(prun "
;;;Same test tracing the on? slots
;;;"
;;;  (loop (= outside*!temp 30) (< outside*!temp 100) (= outside*!temp (+ outside*!temp 10))
;;;        (prn)
;;;        (dumpworld)))

(prun "
Looks OK, but watch what happens even if nothing is going on:
"
  (dumpworld))

;;; Ah, the downside of the functional paradigm: the code runs and runs.
;;; For simple functions that is no problem, but if we build
;;; an entire application this way things bog down (we learned the usual way).
;;;
;;; What we need to do is cache a calculation and then return the cached
;;; result when queried a second time. But then when do we refresh the
;;; cache? Answer: when we have to to stay current with the changing
;;; world arounds us, more prosaically when one of the values used to
;;; calculate the current cache value has changed.
;;;
;;; So we need to keep track of who uses whom in their calculations,
;;; and when one value changes notify its users that they need to
;;; recalculate.
;;;
;;; Next time.



More information about the Cells-cvs mailing list