I've been wanting to learn CLOS lately. So, after reading
Keene's book carefully, I wrote a small space shoot'em up game
using CLOS, lispbuilder-sdl, and cl-opengl. The game itself isn't
noteworthy--though maybe if it was 1979 it would be. However, I wrote
up a large document explaining the code itself, the thought process
involved in writing it, and a bit of a postmortem.
Maybe someone will find it useful.
http://pages.cs.wisc.edu/~psilord/lisp-public/option-9.html
I learned a lot.
Later,
-pete
A couple quick comments on the code.
I think it's not really leveraging CLOS too much to design it around
classes as a bucket of slots, and to work primarily by pulling out the
slots, messing with them, and stuffing them back, as with-accessors
does. I tend to start with the generic functions first, and use defclass
to provide easy implementation of some of the GFs and defmethod for
implementing any more complex behavior.
I don't think it's a good idea to prefix generic function names with the
name of class on which they are intended to work, as you do with
frame-x, frame-y, entity-points, entity-status, etc. They're not very
generic functions if the name has such specific details in it. RADIUS,
X, Y, etc are fine names for generic functions that return the radius,
x, or y of any object.
You don't need override-args. The leftmost keyword argument is used, so
if you want to override keyword args, put the overridden arguments
first, e.g.
(apply #'make-instance foo :bar baz (append overrides initargs))
Zach
With emacs, you could use htmlize to produce font-locked listings.
For example:
http://www.informatimago.com/~pjb/htmlize-example.html
--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
You text-console doesn't handle errors, or process multiple-values, etc.
You could instead use a 'standard' REPL:
http://paste.lisp.org/display/18280
It looks like it uses the stdin/stdout. This could be a problem if we
generate a stand alone executable and launch it from a GUI. Instead,
you could try to launch an xterm for this text-console. This is trivial
to do in clisp, it should not be too hard in sbcl.
-----(build-app.lisp)---------------------------------------------------
(push #P"./" asdf:*central-registry*)
(asdf:oos 'asdf:load-op :option-9)
(sb-ext:save-lisp-and-die
"option-9"
:toplevel (lambda ()
(unwind-protect
(option-9:option-9)
(sb-ext:quit :unix-status 0)))
:executable t)
------------------------------------------------------------------------
This script can be used to generate a stand alone application. But you
will have to launch it from a terminal, otherwise it breaks (at least in
ratpoison, for the banner).
Or we could use:
-----(build-app.lisp)---------------------------------------------------
(push #P"./" asdf:*central-registry*)
(asdf:oos 'asdf:load-op :option-9)
(sb-ext:save-lisp-and-die
"option-9"
:toplevel (lambda ()
(let* ((*terminal-io* (make-two-way-stream
(make-concatenated-stream)
(make-broadcast-stream)A))
(*standard-input* *terminal-io*)
(*standard-output* *terminal-io*)
(*trace-output* *terminal-io*)
(*error-output* *terminal-io*)
(*debug-io* *terminal-io*))
;; and perhaps some other...
(handler-case
(progn (option-9:option-9)
(sb-ext:quit :unix-status 0))
(ext:quit :unix-status 1))))
:executable t)
------------------------------------------------------------------------
but this would disable the current text-console.
So we could instead use a shell wrapper:
-----(build-app.lisp)---------------------------------------------------
(push #P"./" asdf:*central-registry*)
(asdf:oos 'asdf:load-op :option-9)
(sb-ext:save-lisp-and-die
"option-9.bin"
:toplevel (lambda ()
(handler-case
(progn (option-9:option-9)
(sb-ext:quit :unix-status 0))
(error (err)
(princ err) (terpri) (finish-output)
(sb-ext:quit :unix-status 1))))
:executable t)
------------------------------------------------------------------------
------(option-9)--------------------------------------------------------
#!/bin/bash
exec xterm -T option-9 -e rlwrap "$0".bin
------------------------------------------------------------------------
However, again, this works from a terminal, but from ratpoison, the sdl
window doesn't appear, I'm puzzled.
> I think it's not really leveraging CLOS too much to design it around
> classes as a bucket of slots, and to work primarily by pulling out the
> slots, messing with them, and stuffing them back, as with-accessors
> does. I tend to start with the generic functions first, and use defclass
> to provide easy implementation of some of the GFs and defmethod for
> implementing any more complex behavior.
Keene's book makes mention of storing certain data in slots or method
and gives some treatment about when you would choose one over the
other. Since I don't know one way or the other yet beyond clear uses,
I picked one and stuck with it for this project. At least my viewpoint
is understood and I can get good feedback.
> I don't think it's a good idea to prefix generic function names with the
> name of class on which they are intended to work, as you do with
> frame-x, frame-y, entity-points, entity-status, etc.
This is simply because I don't know better yet. Keene's book has
a rather vague treatment on how to name generic functions. At first
it says that you should pick generic names, like "area" for a shape,
but then it says you shouldn't pick generic names, like "reset" for
a device class hierachy. I think it has something to do with if you
think the method lambda-list will always be the same or not. I just
don't have the sense for it yet.
> They're not very generic functions if the name has such specific details in
> it. RADIUS, X, Y, etc are fine names for generic functions that return the
> radius, x, or y of any object.
Interesting. I didn't know that generic function names are pushed to be that
general in practice.
> You don't need override-args. The leftmost keyword argument is used, so
> if you want to override keyword args, put the overridden arguments
> first, e.g.
>
> (apply #'make-instance foo :bar baz (append overrides initargs))
Oh, that I just didn't know. Is that a standard thing?
What I'll likely do is begin converting my code towards the ways that you
folks suggest and rewrite the theory document as I go along.
I do appreciate the comments very much!
-pete
> Interesting. I didn't know that generic function names are pushed to be that
> general in practice.
I've always thought the trick is to have the same name for something
which means the same thing. RADIUS is probably OK for that, but X and
Y might be a bit questionable if the Xs & Ys concerned where not the
same conceptual kind of thing.
Of course, making that kind of distinction is what packages (or other
namespace tools) are for: it's fine, I think to say that in some
package, X means the X coordinate of something, while in some other
package it means the X register, because those are not actually the
same GF.
One problem to think of is that the same methods for widely different
things
should not be part of the same generic function.
> Zach Beane <xa...@xach.com> wrote:
>
>> I don't think it's a good idea to prefix generic function names with the
>> name of class on which they are intended to work, as you do with
>> frame-x, frame-y, entity-points, entity-status, etc.
>
> This is simply because I don't know better yet. Keene's book has
> a rather vague treatment on how to name generic functions. At first
> it says that you should pick generic names, like "area" for a shape,
> but then it says you shouldn't pick generic names, like "reset" for
> a device class hierachy. I think it has something to do with if you
> think the method lambda-list will always be the same or not. I just
> don't have the sense for it yet.
I'd say you should always pick the most generic name you can, partly to
save typing, but also because since it's a generic it's presumably
intended to be specialized and called on a variety of classes. If not,
why is it generic? The other side of the coin is that although it's
generic, you don't want to specialize the same generic function to do
totally different operations depending on the class (such as returning
the value of slot X for class A and putting an X in a checkbox for class
B). Using packages can avoid that problem. FRAME:X could be a nicer
name, or even FRAME::X, since sometimes accessors are only needed for
implementing the user API.
May I use this in an Apache V2 licensed open source software product
if the source is released and I assign credit? Also, in general,
would you prefer that I ask you every time I would like to use your
software snippets whose license is unknown? Or may I just assume that
I can use it under in the context just mentioned?
> It looks like it uses the stdin/stdout. This could be a problem if we
> generate a stand alone executable and launch it from a GUI. Instead,
> you could try to launch an xterm for this text-console. This is trivial
> to do in clisp, it should not be too hard in sbcl.
I believe I know how to do this with sb-ext:run-program, so it shouldn't
be a problem.
Thank you for your suggesstions.
So far, I've made a lot of changes to the source. I'll have a new theory
of operation document with new sources up in a day or two.
I do have one question though.
Suppose I have this example--'a' is another generic method:
(defmethod foobar ((e thingy))
(when (a thingy)
(setf (b thingy) (* (a thingy) 2))
(incf (a thingy))))
If I have a lot of repeated (a thingy) forms, is it better to:
(with-accessors ((a a)) thingy
(when a
(setf (b thingy) (* a 2))
(incf a)))
Or:
(symbol-macrolet ((a '(a thingy)))
(when a
(setf (b thingy) (* a 2))
(incf a)))
Thank you.
-pete
> Pascal J. Bourguignon <p...@informatimago.com> wrote:
>> Peter Keller <psi...@cs.wisc.edu> writes:
>>
>>> I've been wanting to learn CLOS lately. So, after reading
>>> Keene's book carefully, I wrote a small space shoot'em up game
>>> using CLOS, lispbuilder-sdl, and cl-opengl. The game itself isn't
>>> noteworthy--though maybe if it was 1979 it would be. However, I wrote
>>> up a large document explaining the code itself, the thought process
>>> involved in writing it, and a bit of a postmortem.
>>>
>>> Maybe someone will find it useful.
>>>
>>> http://pages.cs.wisc.edu/~psilord/lisp-public/option-9.html
>>>
>>> I learned a lot.
>>
>> You text-console doesn't handle errors, or process multiple-values, etc.
>>
>> You could instead use a 'standard' REPL:
>>
>> http://paste.lisp.org/display/18280
>
> May I use this in an Apache V2 licensed open source software product
> if the source is released and I assign credit? Also, in general,
> would you prefer that I ask you every time I would like to use your
> software snippets whose license is unknown? Or may I just assume that
> I can use it under in the context just mentioned?
Here, I write the word "word". Oops, now it's under the GPL! You cannot
use the word "word" anymore, unless you provide the source of your
thinking process!
Those two forms should compile to exactly the same code.
On the other hand, your original function may (or may not, it would
depend on the possible side effects of the method of the generic
function A) be equivalent to:
(defmethod foobar ((thingy e)) ; and you probably want
; a thingy of class e,
; instead of a e of class thingy.
(let ((a-of-thingy (a thingy)))
(unwind-protect
(when a-of-thingy
(setf (b thingy) (* a-of-thingy 2))
(incf a-of-thingy))
(setf (a thingy) a-of-thingy))))
:)
>> I do have one question though.
>>
>> Suppose I have this example--'a' is another generic method:
>>
>> (defmethod foobar ((e thingy))
>> (when (a thingy)
>> (setf (b thingy) (* (a thingy) 2))
>> (incf (a thingy))))
>>
>> If I have a lot of repeated (a thingy) forms, is it better to:
>>
>> (with-accessors ((a a)) thingy
>> (when a
>> (setf (b thingy) (* a 2))
>> (incf a)))
>>
>> Or:
>>
>> (symbol-macrolet ((a '(a thingy)))
>> (when a
>> (setf (b thingy) (* a 2))
>> (incf a)))
>
> Those two forms should compile to exactly the same code.
Ok, that's what I thought it would do. it is good you confirmed it.
> On the other hand, your original function may (or may not, it would
> depend on the possible side effects of the method of the generic
> function A) be equivalent to:
>
> (defmethod foobar ((thingy e)) ; and you probably want
> ; a thingy of class e,
> ; instead of a e of class thingy.
> (let ((a-of-thingy (a thingy)))
> (unwind-protect
> (when a-of-thingy
> (setf (b thingy) (* a-of-thingy 2))
> (incf a-of-thingy))
> (setf (a thingy) a-of-thingy))))
Interesting, I'll have to keep this in mind!
Thank you.
-pete
> If I have a lot of repeated (a thingy) forms, is it better to:
>
> (with-accessors ((a a)) thingy
> (when a
> (setf (b thingy) (* a 2))
> (incf a)))
>
> Or:
>
> (symbol-macrolet ((a '(a thingy)))
> (when a
> (setf (b thingy) (* a 2))
> (incf a)))
I'd probably use WITH-SLOTS (I can dot that, because usually my slots
have the same name as the corresponding reader/accessor).
Nicolas
Is having the accessor name the same name as the slot name a common thing in
CLOS?
The reason why I thought it was ok to originally name my stuff like
frame-x, entity-points, etc, was because of Keene's book where she often
did things like:
slot name accessor name
--------- -------------
name lock-name
owner lock-owner
level lock-level
If you read into what she wrote, it seems there is some kind of
a name choice she is making between the external protocol and the
internal protocol. However she never clearly (or I missed it) states
her reasoning about it.
Thank you.
-pete
This is a pretty bad naming convention in practice because it breaks
information hiding: Usually, as a client of a class, you shouldn't have
to know where a particular function/method was introduced deep down in
the class hierarchy. Worse, code refactoring may have to move such
methods to classes different from where they were originally introduced:
Would such methods then have to be renamed, and all client code as well,
or would it be ok to have an inconsistency in the naming convention?
It's better to avoid such problems altogether.
There is an exception when conceptually abstract classes are involved
that are implemented by concrete subclasses: Then it is ok that the
abstract classes introduce accessor names with their class names as
prefixes.
There is also an exception for pedagogical purposes: It can be confusing
for newbies to have the same name for slots and accessors, so it can be
useful to use such a naming convention for introductory purposes.
Note that using the same name for slots and accessors is also not ideal:
You may want to export the name of an accessor from a package without
exporting the corresponding slot name at the same time, so that
(slot-value object 'slot) cannot easily be called from client code.
Pascal
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
Is there an ideal solution? Should I name my slots s-XXX and just have the
accessors be XXX so I can both A) Not put the class names into the accessor
functions and B) Not make it so both the accessor and slot name are exported
with the same symbol?
Is there a convention for slot names and accessor values which I am missing?
-pete
I took into consideration a fair amount of the suggestions, updated my
code to 0.2, and rewrote some of the theory of operation document using
the new codes. The code is MUCH cleaner and has a much better feel.
Hopefully now it is closer to being something someone can read and
gain benefit.
I feel I've learned a few more things about CLOS and the effective
use of it. I appreciate all of your comments. Thank you.
I haven't yet done the making it into an executable and/or reconciling
the text console feature with gui environments yet. Hopefully soon.
Thank you.
-pete
> Is there a convention for slot names and accessor values which I am missing?
I don't think there is - I think you just need to think about what the
names in the context of your program want to look like.
> Peter Keller <psi...@cs.wisc.edu> wrote:
>> http://pages.cs.wisc.edu/~psilord/lisp-public/option-9.html
>
> I took into consideration a fair amount of the suggestions, updated my
> code to 0.2, and rewrote some of the theory of operation document using
> the new codes. The code is MUCH cleaner and has a much better feel.
I like how it looks! Here are a few comments on superficial issues.
Rather than checking values against some property of an object, e.g.
(eq (status fist) :alive), I'd be inclined to add gfs to give that
information. There could be ALIVEP and DEADP gfs, and a KILL gf to make
an alive object dead. (Though the dictionary says STALE can be a verb, I
think MAKE-STALE would be easier to understand.) Same with checking that
the shield value is positive; I'd be inclined to add a DEPLETEDP gf that
is false until the shield is fully depleted.
You have three identical ABSORBS method on each subclass of shield. You
could do with just a single method that either specializes on shield
instances or doesn't specialize at all.
I'd be inclined to add a DISTANCE gf that returns the distance between
any object that specializes methods for X and Y.
There are other things I'd like to comment on after I review it more.
Zach
Ok, I know how to do all of the above and it makes sense.
> You have three identical ABSORBS method on each subclass of shield. You
> could do with just a single method that either specializes on shield
> instances or doesn't specialize at all.
I'm not entirely sure how to apply what you've told me. The collision graph
looks like:
collidee| shot-shield | ship-shield
collider |
--------------------------------------------
shot | absorbed | absorbed
--------------------------------------------
ship | | absorbed
How do I do that with only one defmethod for absorbs?
> I'd be inclined to add a DISTANCE gf that returns the distance between
> any object that specializes methods for X and Y.
I don't understand how to write this defmethod, specifically how to force
it to specialize on "any object that specializes methods for X and Y". Could
you provide an example?
> There are other things I'd like to comment on after I review it more.
I appreciate your comments!
Thank you.
-pete
>> You have three identical ABSORBS method on each subclass of shield. You
>> could do with just a single method that either specializes on shield
>> instances or doesn't specialize at all.
>
> I'm not entirely sure how to apply what you've told me. The collision graph
> looks like:
>
> collidee| shot-shield | ship-shield
> collider |
> --------------------------------------------
> shot | absorbed | absorbed
> --------------------------------------------
> ship | | absorbed
>
> How do I do that with only one defmethod for absorbs?
You could just write it as:
(defmethod absorbs (collider (collidee shield))
(when (> (shots-absorbed collidee) 0)
(decf (shots-absorbed collidee)))
(values t (zerop (shots-absorbed collidee))))
What should happen for ship/shot-shield? Nothing? An error? Whatever it
should do, you can add a method that does it.
>> I'd be inclined to add a DISTANCE gf that returns the distance between
>> any object that specializes methods for X and Y.
>
> I don't understand how to write this defmethod, specifically how to force
> it to specialize on "any object that specializes methods for X and Y". Could
> you provide an example?
Something like this:
(defgeneric x (object))
(defgeneric y (object))
(defgeneric distance (point1 point2))
(defmethod distance (point1 point2)
(sqrt (+ (expt (- (x point1) (x point2)) 2)
(expt (- (y point1) (y point2)) 2)))))
Any object that specializes X and Y now can get meaningful results from
the DISTANCE generic function without any additional code.
Zach
> [...] I'd be inclined to add a DEPLETEDP gf that is false until the
> shield is fully depleted.
Better: DEPLETED-P. (IIRC the usual convention is to use the hyphen if
the base word has more than one syllable.)
Nicolas
No, that's incorrect.
From the introduction to Chapter 6 in CLtL2:
"By convention, the names of predicates usually end in the letter p
(which stands for ``predicate''). Common Lisp uses a uniform convention
in hyphenating names of predicates. If the name of the predicate is
formed by adding a p to an existing name, such as the name of a data
type, a hyphen is placed before the final p if and only if there is a
hyphen in the existing name. For example, number begets numberp but
standard-char begets standard-char-p. On the other hand, if the name of
a predicate is formed by adding a prefixing qualifier to the front of an
existing predicate name, the two names are joined with a hyphen and the
presence or absence of a hyphen before the final p is not changed. For
example, the predicate string-lessp has no hyphen before the p because
it is the string version of lessp (a MacLisp function that has been
renamed < in Common Lisp). The name string-less-p would incorrectly
imply that it is a predicate that tests for a kind of object called a
string-less, and the name stringlessp would connote a predicate that
tests whether something has no strings (is ``stringless'')!"
> No, that's incorrect.
>
> From the introduction to Chapter 6 in CLtL2:
>
> "By convention, the names of predicates usually end in the letter p (which
> stands for ``predicate''). Common Lisp uses a uniform convention in
> hyphenating names of predicates. If the name of the predicate is formed by
> adding a p to an existing name, such as the name of a data type, a hyphen
> is placed before the final p if and only if there is a hyphen in the
> existing name. For example, number begets numberp but standard-char begets
> standard-char-p. On the other hand, if the name of a predicate is formed by
> adding a prefixing qualifier to the front of an existing predicate name,
> the two names are joined with a hyphen and the presence or absence of a
> hyphen before the final p is not changed. For example, the predicate
> string-lessp has no hyphen before the p because it is the string version of
> lessp (a MacLisp function that has been renamed < in Common Lisp). The name
> string-less-p would incorrectly imply that it is a predicate that tests for
> a kind of object called a string-less, and the name stringlessp would
> connote a predicate that tests whether something has no strings (is
> stringless'')!"
I stand corrected.
Thanks, Nicolas
I understand now.
In the original implementation I wrote, I made it so the most general
absorbs method was the "does not absorb the collider and shield gets
destroyed" outcome. Then I produced three specific methods for each
combination in the above table.
In what you suggested, you reversed the boolean sense of the methods.
The most general method's outcome is that the shield absorbs the hit,
and the only specific method is the one in which the ship collider destroys
the shot-shield.
It took me a bit to see what you were getting at, but I believe I have
more understanding.
I've implemented the boolean sense change in the methods and it has worked.
>>> I'd be inclined to add a DISTANCE gf that returns the distance between
>>> any object that specializes methods for X and Y.
>>
>> I don't understand how to write this defmethod, specifically how to force
>> it to specialize on "any object that specializes methods for X and Y". Could
>> you provide an example?
>
> Something like this:
>
> (defgeneric x (object))
> (defgeneric y (object))
> (defgeneric distance (point1 point2))
>
> (defmethod distance (point1 point2)
> (sqrt (+ (expt (- (x point1) (x point2)) 2)
> (expt (- (y point1) (y point2)) 2)))))
>
> Any object that specializes X and Y now can get meaningful results from
> the DISTANCE generic function without any additional code.
I see. The reason I originally misunderstood was because I forgot
that accessor functions are themselves generic functions. The FRAME
class already has an X and Y accessor, so I now see what you mean
and have implemented the idea.
I'm pretty sure there are some other things that I need fixing, but
with these last changes, I think I'm going to fixate the explanation
document to version 0.3 which will contain these changes (along with
the ALIVEP & friends change) and leave it that way.
Of course, if some significant advancement in the game actually
happens feature wise, I might make another version of the postmortem.
It is kinda cool, though, that the more I talk to you folks about
how I write my lisp code, the less code with the same functionality
there is afterwards. :)
Thank you.
-pete
I just tinkered with htmlize. It work out of the box and its usefulness
is obvious to me. My future lisp related blog/article posts will likely
utilize it once I figure out the best way to get it into my workflow.
Thank you!
-pete
I find org-mode useful for writing blog posts with Lisp code:
http://tkpapp.blogspot.com/2009/12/syntax-highlighting-with-org-mode.html
Tamas
> Is having the accessor name the same name as the slot name a common
> thing in CLOS?
>
> The reason why I thought it was ok to originally name my stuff like
> frame-x, entity-points, etc, was because of Keene's book where she often
> did things like:
>
> slot name accessor name
> --------- -------------
> name lock-name
> owner lock-owner
> level lock-level
[ one of the reasons I don't like this book much ]
Besides all the very good arguments other people have provided, you also
need to ask yourself the question of readability. I find that looking at
your code and asking yourself
"is this easy to read and understand for
- me (one year later),
- somebody who doesn't know it,
- somebody who doesn't do computers?"
is really helpful for deciding on a specific coding style.
For instance, consider this:
(defclass car () ((color :accessor car-color)))
(defclass hair () ((color :accessor hair-color)))
versus this:
(defclass car () ((color :accessor color)))
(defclass hair () ((color :accessor color)))
What would you prefer to read? This:
(car-color car)
(hair-color hair)
or this:
(color car)
(color hair)
Something else which I find very important to understand is that you
must not consider problems like accessor naming in isolation. Every
problem you address is closely related to the rest of your code. Here,
in particular, variables naming.
Sometimes, my students find it difficult to accept the preferred style
above. The reaction in that case is usually that on the contrary, they
like to have type information in the function name because it makes
things like (car-color x) more readable [ than just (color x) ].
Surprisingly enough, they don't realize that x /is/ the very bad name
here, not color.
So I guess the only rule that matters is to carefully name your objects
according to what you know of them when you write the code, and what you
need to express about them in order to make your code readable. You will
also realize that the better the naming scheme, the less documentation
required ;-)
--
Resistance is futile. You will be jazzimilated.
Scientific site: http://www.lrde.epita.fr/~didier
Music (Jazz) site: http://www.didierverna.com
> Is there an ideal solution?
Nope. Like I said earlier, the "ideal" is when your code is as
readable as possible, but whatever general policy you decide on, there
will always be unsatisfactory corner cases. For instance, from time to
time, I'd like to use a name for an accessor which unfortunately already
names a standard CL function.
> Should I name my slots s-XXX and just have the accessors be XXX so I
> can both A) Not put the class names into the accessor functions and B)
> Not make it so both the accessor and slot name are exported with the
> same symbol?
In general (I insist on "In general"), I don't think that exporting
both the accessor and the slot name at the same time is a serious
problem, because if you're using an external library, you shouldn't use
slot-value anyway (it relveals too much of the underlying
implementation). Instead you should complain if the library doesn't
provide the accessors you need, either readers, writers, or both.
Even in my own code I always go through accessors (again, "in general"),
with one exception: when a slot is considered to be a logical constant
(or something that is not supposed to change, except on very rare
occasions), I only define a reader and use slot-value when I still need
to write in it.
> Besides all the very good arguments other people have provided, you also
> need to ask yourself the question of readability. I find that looking at
> your code and asking yourself
>
> "is this easy to read and understand for
> - me (one year later),
> - somebody who doesn't know it,
> - somebody who doesn't do computers?"
>
> is really helpful for deciding on a specific coding style.
Actually, I think that's almost the *only* thing to bear in mind, at
least with regards to naming concentions: the system doesn't care, but
the humans who have to maintain the code do.
After a moderate amount of feedback, both on and off the group, I reworked
the codes some more and it now sits at version 0.5.
I know it still isn't a good CLOS program. But hey, it's the first CLOS
code I ever wrote, so I have to start somewhere. :)
I am starting to get the idea of nouns and verbs when a common lisper speaks
of it in relation to CLOS, but I haven't yet grokked the depth to which generic
functions are pushed.
I'll get there eventually. I very much appreciated the feedback. Thank you.
-pete