[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