I've run across something I don't understand how to do in lisp.
Suppose I have two special variables:
(defparameter *foobar* (list 1 2 3 4 5))
(defparameter *qux* (list 6 7 8 9 10))
How do I write a function foo so it works like this:
* (foo *foobar*)
(1 3 5)
* *foobar*
(1 3 5)
* (foo *qux*)
(7 9)
* *qux*
(7 9)
I know that if I do something like this:
(defun foo (*xxx*)
(declare (special *xxx))
(setf *xxx* (remove-if #'evenp *xxx*)))
That *xxx* will be considered special, but this knowledge isn't good enough
for me to write foo properly.
How is this typically done?
Thank you.
-pete
It typically isn't done. But if you really want to do this, you want to
read about the function called SET.
rg
This is not done, it wouldn't be good functional style.
If you really want to do it, you could do:
(defun foo (list)
(let ((new (remove-if (function evenp) list)))
(setf (car list) (car new)
(cdr list) (cdr new))
list))
but this is bad, because now if you write:
(let ((saved *foobar*))
(foo *foobar*)
saved)
--> (1 3 5) ; instead of (1 2 3 4 5).
The thing is that in lisp, all the parameters are always passed by
value. Never by reference. (But as you can see, some of these values
are references, so when it happens, you can modify the refered object).
Otherwise, you could use closures. Have a look at:
http://groups.google.com/group/comp.lang.lisp/msg/d141c5636559ba3b
And finally, as Spamivore mentionned, you can also use SET or (setf
symbol-value), but this would work only on global variables. Closure
would work on lexical variables too, and even on places.
--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
> Suppose I have two special variables:
>
> (defparameter *foobar* (list 1 2 3 4 5))
> (defparameter *qux* (list 6 7 8 9 10))
>
> How do I write a function foo so it works like this:
>
> * (foo *foobar*)
> (1 3 5)
> * *foobar*
> (1 3 5)
> * (foo *qux*)
> (7 9)
> * *qux*
> (7 9)
It's not quite clear what you're asking for. For example, it'd be
possible to do this fairly easily as follows.
(defun foo (list)
(do ((head (cons nil list))
(scan list (cdr scan)))
((endp scan) (setf (cdr head) nil) list)
(when (oddp (car scan))
(pop head)
(setf (car head) (car scan)))))
This will work as long as there's at least one ODDP element in the input
list, or the input list is empty anyway. It doesn't depend on the
specialness of any variables: it just clobbers the input list
sufficiently hard.
Indeed, a /function/ has its arguments evaluated, so it can't possibly
tell whether its argument came from a special variable, a lexical
variable, or was consed up by some hairy single-use expression.
> I know that if I do something like this:
>
> (defun foo (*xxx*)
> (declare (special *xxx))
> (setf *xxx* (remove-if #'evenp *xxx*)))
>
> That *xxx* will be considered special, but this knowledge isn't good
> enough for me to write foo properly.
That will bind *XXX* and set its value -- and then unbind it again,
restoring the old value so nobody will notice.
> How is this typically done?
If you provide the variable name, rather than its value, to the function
then you can use SYMBOL-VALUE to alter it.
(defun foo* (name)
(setf (symbol-value name)
(remove-if #'evenp (symbol-value name))))
but now you have to call
(foo* '*foobar*)
to QUOTE the variable name (though you can paper over this with macros
if you like -- it's probably good to leave the functional version
accessible somewhere, though). And this will only work with special
variables (because a symbol's value slot only contains the value of the
dynamic variable).
-- [mdw]
After reading all the responses, I see that I am going against the
grain of the language. I will find some other solution (of which I
know some more typical ones) for the book keeping and transformation
of my data structures.
My original codes started out as a tiny hack which evolved
somewhat. It just looks like it is time to refactor a little bit.
Thank you.
-pete
Does it have to be a function? Are you willing to use a macro?
lisp> (defparameter *foobar* (list 1 2 3 4 5))
*FOOBAR*
lisp> (defparameter *qux* (list 6 7 8 9 10))
*QUX*
lisp> (defmacro foo (v) `(setq ,v (remove-if #'evenp ,v)))
FOO
lisp> (foo *foobar*)
(1 3 5)
lisp> *foobar*
(1 3 5)
lisp> (foo *qux*)
(7 9)
lisp> *qux*
(7 9)
_______________________________________________________________________________
Don Geddis http://don.geddis.org/ d...@geddis.org
Women want you to be honest about your feelings the way the IRS wants you to
be honest about your finances.
This is in fact what I'm currently doing. But you can't map a macro.
I was trying to shorten a pile of code which looks like this:
(foo *a*)
(foo *b*)
(foo *c*)
(foo *d*)
(foo *e*)
etc...
-pete
> Hello,
>
> I've run across something I don't understand how to do in lisp.
>
> Suppose I have two special variables:
>
> (defparameter *foobar* (list 1 2 3 4 5))
> (defparameter *qux* (list 6 7 8 9 10))
>
> How do I write a function foo so it works like this:
>
> * (foo *foobar*)
You would write
(setq *foobar* (foo *foobar*))
Function side effects on variables have to be done where the variables
are visible.
You can write location modifying forms using SETF expanders, if you
really need to do that, but mostly you just arrange to set variables to
the return values of the functions.
Since Lisp supports multiple-value returns, you can even set multiple
values this way.
> (1 3 5)
> * *foobar*
>
> (1 3 5)
> * (foo *qux*)
>
> (7 9)
> * *qux*
>
> (7 9)
>
>
> I know that if I do something like this:
>
> (defun foo (*xxx*)
> (declare (special *xxx))
> (setf *xxx* (remove-if #'evenp *xxx*)))
>
> That *xxx* will be considered special, but this knowledge isn't good enough
> for me to write foo properly.
Well, this only works for the variable *XXX* and it doesn't matter what
you call the variable that is the argument to FOO. That is because the
first argument to SETF is not evaluated
--
Thomas A. Russ, USC/Information Sciences Institute
> It's not quite clear what you're asking for. For example, it'd be
> possible to do this fairly easily as follows.
>
> (defun foo (list)
> (do ((head (cons nil list))
> (scan list (cdr scan)))
> ((endp scan) (setf (cdr head) nil) list)
> (when (oddp (car scan))
> (pop head)
> (setf (car head) (car scan)))))
>
> This will work as long as there's at least one ODDP element in the input
> list, or the input list is empty anyway. It doesn't depend on the
> specialness of any variables: it just clobbers the input list
> sufficiently hard.
Yes, but it will fail if there are NO ODDP elements. In other words,
there has to be at least one element in the returned list. This is
related to the entire issue about not being able to produce a NIL list
in a destructive manner from a non-NIL list.
> This is in fact what I'm currently doing. But you can't map a macro.
> I was trying to shorten a pile of code which looks like this:
>
> (foo *a*)
> (foo *b*)
> (foo *c*)
> (foo *d*)
> (foo *e*)
> etc...
So what do you have here? A list of values, or an array of values?
Ron G. suggested SET to you. Does this work for you?
(defun foo (v)
(set v ...) )
(apply #'foo '(*a* *b* *c* *d* *e* ...))
_______________________________________________________________________________
Don Geddis http://don.geddis.org/ d...@geddis.org
Teaching kids by age makes about as much sense as teaching them by height.
> m...@distorted.org.uk (Mark Wooding) writes:
>
> > This will work as long as there's at least one ODDP element in the
> > input list, or the input list is empty anyway.
>
> Yes, but it will fail if there are NO ODDP elements.
Err, yes. That's pretty much what I said, isn't it?
> In other words, there has to be at least one element in the returned
> list.
It suffices for the input list to have been empty anyway.
> This is related to the entire issue about not being able to produce a
> NIL list in a destructive manner from a non-NIL list.
Yes, of course.
If you were just trying to clarify, thanks; if you thought that there
was something to correct, could you be more specific, please?
-- [mdw]
> Peter Keller <psi...@cs.wisc.edu> wrote on Thu, 16 Dec 2010:
> > This is in fact what I'm currently doing. But you can't map a macro.
> > I was trying to shorten a pile of code which looks like this:
> > (foo *a*)
> > (foo *b*)
> > (foo *c*)
> > (foo *d*)
> > (foo *e*)
> > etc...
>
> Ron G. suggested SET to you. Does this work for you?
>
> (defun foo (v)
> (set v ...) )
>
> (apply #'foo '(*a* *b* *c* *d* *e* ...))
Whether this works or not, you (Peter) are almost certainly doing
something wrong. What is it you're actually trying to accomplish?
rg
Put the mapping in the macro:
(defmacro foo (&rest vars)
`(progn ,@(loop for v in vars
collect `(setq ,v (remove-if #'evenp ,v)))))
(foo *a* *b* *c* ...)
--
Barry Margolin, bar...@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***
> If you were just trying to clarify, thanks; if you thought that there
> was something to correct, could you be more specific, please?
I was elaborating on your answer. No correction.
I completely believe I'm doing something wrong. :)
I'm going to change the organization of the code away from what I
am about to describe with a major refactor, but here is how I got to
asking the original question:
I wrote a small minigame--space invaders-like, because I was mainly
interested in learning about lispbuilder-sdl and cl-opengl. The game
works and I was refactoring it slightly (before I relized it needed
a much more extensive refactoring). So:
I store various objects (as with defstruct) in the game in various
special variables. (As an aside, this style of lisp, as best as I can
figure, was popular in the "AI Winter" period of lisp, where lots of
stuff was kept in global special variables. I happened to write in
this method for this game out of nothing other than random choice. I
usually exhibit a "everything is a closure" scheme point of view in
my lisp, but I wanted to try something different. I make no claim
that what I did was good lisp code design or that I perceived lisp
style during that time correctly.)
(defparameter *player* nil)
(defparameter *player-shots* nil)
(defparameter *enemies* nil)
(defparameter *enemy-shots* nil)
(defparameter *particles* nil)
So, to draw the objects, which are pushed onto lists rooted at each special
variable:
(defun draw-object (o s)
(with-accessors ((ox point-x) (oy point-y)) (object-origin o)
(destructuring-bind (sx sy) s
(gl:with-primitive :line-loop
(mapc #'(lambda (p)
(with-accessors ((vx point-x) (vy point-y)) p
(gl:vertex (+ ox (* vx sx)) (+ oy (* vy sy)) 0.0)))
(object-vtxs o))))))
and then draw each object in each list with a scaling factor:
(defun draw-objects-with-scale (scale &rest objects)
(mapc #'(lambda (l)
(mapc #'(lambda (o)
(when o (draw-object o scale)))
l))
objects))
Then I'd write:
(draw-objects-with-scale
`(.01 .01)
*player*
*player-shots*
*enemies*
*enemy-shots*
*particles*
*score-display*
*high-score-display*)
So, suppose I run some collision algorithms between some of the above sets
and mark some of the objects as dead. I want to clean them up and remove
them from the special variables:
(defmacro remove-dead/stale-objects (lst)
`(setf ,lst
(remove-if
#'(lambda (o)
(when (eq (object-status o) :dead)
(affect-score (object-score o)))
(multiple-value-bind (x y) (location o)
(or (eq (object-status o) :dead)
(eq (object-status o) :stale)
(< x -.05) (> x 1.05) (< y -.05) (> y 1.05))))
,lst)))
So, I'd call it like:
(remove-dead/stale-objects *player-shots*)
(remove-dead/stale-objects *enemies*)
(remove-dead/stale-objects *enemy-shots*)
(remove-dead/stale-objects *player*)
(remove-dead/stale-objects *particles*)
remove-dead/stale-objects can ultimately result in ALL objects being
removed and the special variable being set to nil. Or it could be
initially called on a nil list.
At this point, I saw the repeated evaluation of the lst variable in the
above macro and immediately wanted to perform the usual idiom:
(let ((l (gensym)))
`(let ((,l ,lst)) ... ))
I wanted to mix remove.../..objects with the way that I wrote
draw-objects-with-scale and the &optional parameter to save having to
write out the function call every time.
And then I got stuck, because I was processing special variables and
didn't know how to capture them so setf would work in the manner that
I expected but not perform repeated evaluation. I hadn't even gotten to
define-modify-macro, which I haven't begun to understanding fully yet.
But anyways, that's how I got to the original question.
The obvious solution to the reorganization is to either use CLOS, or to
hold each group of objects in its own closure which knows how to remove
dead objects from it internally. I'll move my stuff that way, but I'm
interested in what you guys say about the mess which led me here. :)
Thank you.
-pete
Do you mean mapc?
That does work, but I'm going to have to explore why. I haven't used set
before.
Thank you.
-pete
> I store various objects (as with defstruct) in the game in various
> special variables. (As an aside, this style of lisp, as best as I can
> figure, was popular in the "AI Winter" period of lisp, where lots of
> stuff was kept in global special variables. I happened to write in
> this method for this game out of nothing other than random choice. I
> usually exhibit a "everything is a closure" scheme point of view in
> my lisp, but I wanted to try something different. I make no claim
> that what I did was good lisp code design or that I perceived lisp
> style during that time correctly.)
In general you should avoid global variables.
When you have objects, you just don't need global variables, and this
includes mere lisp objects, not necessarily CLOS objects.
> (defparameter *player* nil)
> (defparameter *player-shots* nil)
> (defparameter *enemies* nil)
> (defparameter *enemy-shots* nil)
> (defparameter *particles* nil)
You can easily reify the game:
(defclass space-invader-game ()
((player :initform (make-instance 'player)
:reader space-invader-player
:documentation "The player of the game.")
(player-shots ...)
(enemies ...)
...))
You don't even need a global variable for this space-invader-game
instance:
(defun play ()
(let ((game (make-instance 'space-invader-game)))
(play-game game)))
However, you might want to keep the root object (the game in your
program), in a global variable only to ease debugging:
(defvar *game*)
(defun play ()
(let ((*game* (make-instance 'space-invader-game)))
(play-game *game*)))
> Then I'd write:
>
> (draw-objects-with-scale
> `(.01 .01)
> *player*
> *player-shots*
> *enemies*
> *enemy-shots*
> *particles*
> *score-display*
> *high-score-display*)
See! This kind of function call is a sure sign you need to reify!
> So, suppose I run some collision algorithms between some of the above sets
> and mark some of the objects as dead. I want to clean them up and remove
> them from the special variables:
>
> (defmacro remove-dead/stale-objects (lst)
> `(setf ,lst
> (remove-if
> #'(lambda (o)
> (when (eq (object-status o) :dead)
> (affect-score (object-score o)))
>
> (multiple-value-bind (x y) (location o)
> (or (eq (object-status o) :dead)
> (eq (object-status o) :stale)
> (< x -.05) (> x 1.05) (< y -.05) (> y 1.05))))
> ,lst)))
Having an object doesn't change your problem. We've discussed the
solutions.
The idiomatic way, is to use setf outside of the function computing the
new list:
(with-slots (player-shots enemies enemy-shots particules) game
(setf player-shots (remove-dead/state-objects player-shots)
enemies (remove-dead/state-objects enemies)
enemy-shots (remove-dead/state-objects enemy-shots)
...))
Now, if you apply the same function to all these objects, it's also a
sure sign that you have a single kind of objects. So the question
really, is why you keep them in different lists?
(defclass space-invader-game ()
((player :initform (make-instance 'player)
:reader space-invader-player
:documentation "The player of the game.")
(mobiles :initform '() :accessor mobiles
:documentation "All the objects living in the game.")
(score-display ...)
(high-score-display ...)))
So that you only have to call the methods once:
(with-slots (mobiles particules) game
(setf mobiles (remove-dead/state-objects mobiles)))
> The obvious solution to the reorganization is to either use CLOS, or to
> hold each group of objects in its own closure which knows how to remove
> dead objects from it internally.
Closure and objects are equivalent. I'd advise to use CLOS.
> I'll move my stuff that way, but I'm
> interested in what you guys say about the mess which led me here. :)
A simulation game with objects moving around should be programmed in OO.
OO has been invented exactly for this kind of application, with simula.
Isn't that the truth.... As a small aside, the Land of Lisp book
seems to prefer using globals more than I would have... It is one
of the reasons why I thought this was a reasonably acceptable way to
write lisp.
> When you have objects, you just don't need global variables, and this
> includes mere lisp objects, not necessarily CLOS objects.
>
>
>> (defparameter *player* nil)
>> (defparameter *player-shots* nil)
>> (defparameter *enemies* nil)
>> (defparameter *enemy-shots* nil)
>> (defparameter *particles* nil)
>
> You can easily reify the game:
>
> (defclass space-invader-game ()
> ((player :initform (make-instance 'player)
> :reader space-invader-player
> :documentation "The player of the game.")
> (player-shots ...)
> (enemies ...)
> ...))
Yep. I wasn't going to use CLOS (though maybe I will). I was going
to do the same thing with a defstruct.
> You don't even need a global variable for this space-invader-game
> instance:
>
> (defun play ()
> (let ((game (make-instance 'space-invader-game)))
> (play-game game)))
>
> However, you might want to keep the root object (the game in your
> program), in a global variable only to ease debugging:
Yeah, it turns out I implemented a cheapy text console with eval
that allows me to pause the game, type in raw lisp, spawn enemies,
change AI functions, debug stuff, rewrite a function if I want to,
and then start the game back up again when I leave the console.
I might leave just enough of an attachment of the game structures to
a special global just so I kep this behavior.
>> Then I'd write:
>>
>> (draw-objects-with-scale
>> `(.01 .01)
>> *player*
>> *player-shots*
>> *enemies*
>> *enemy-shots*
>> *particles*
>> *score-display*
>> *high-score-display*)
>
> See! This kind of function call is a sure sign you need to reify!
I agree.
> Having an object doesn't change your problem. We've discussed the
> solutions.
> The idiomatic way, is to use setf outside of the function computing the
> new list:
>
> (with-slots (player-shots enemies enemy-shots particules) game
> (setf player-shots (remove-dead/state-objects player-shots)
> enemies (remove-dead/state-objects enemies)
> enemy-shots (remove-dead/state-objects enemy-shots)
> ...))
Ah! Thanks for the idiomatic construction. I'll try and remember that for
the future.
> Now, if you apply the same function to all these objects, it's also a
> sure sign that you have a single kind of objects. So the question
> really, is why you keep them in different lists?
I keep them in different 'sets' because of collision detection. I only
want a certain set of objects to be considered for collision detection
with a certain set of other objects.
For example:
(collide *player-shots* *enemy-shots*)
(collide *player-shots* *enemies*)
(collide *enemy-shots* *player*)
(collide *enemies* *player*)
Any other combination has no effect for collisions.
>> The obvious solution to the reorganization is to either use CLOS, or to
>> hold each group of objects in its own closure which knows how to remove
>> dead objects from it internally.
>
>
> Closure and objects are equivalent. I'd advise to use CLOS.
I just finished reading the Keene CLOS book, so I might give CLOS a
try for this little game. No time to learn like the present.
>> I'll move my stuff that way, but I'm
>> interested in what you guys say about the mess which led me here. :)
>
> A simulation game with objects moving around should be programmed in OO.
> OO has been invented exactly for this kind of application, with simula.
Fair enough.
Thank you for the conversation!
-pete
> Pascal J. Bourguignon <p...@informatimago.com> wrote:
> > You can easily reify the game:
> >
> > (defclass space-invader-game ()
>
> Yep. I wasn't going to use CLOS (though maybe I will). I was going
> to do the same thing with a defstruct.
I'd strongly suggest using DEFCLASS instead for a first cut. The main
difference is that you can redefine a DEFCLASS class, adding, removing,
or modifying slots at will, interactively in a running Lisp. With
DEFSTRUCT, if you're lucky, you'll get some warning about the
redefinition, and you risk really unpleasant breakages due to (a) stale
instances of old versions of the structure, or (b) new instances being
passed to functions which inlined slot accessors according to an old
structure layout.
Once you've decided that you're definitely happy with the slots you've
defined and their types and readonlyness and so on, and if the
performance you're getting isn't adequate, then DEFSTRUCT is a good way
to go. Until then, DEFCLASS every time.
(The other good use of DEFSTRUCT is with the :TYPE option, to define
accessors for bits of list or vector structure; but this isn't a
particularly common case.
It's annoying that DEFCLASS is so much more verbose than DEFSTRUCT. I
like the way that DEFSTRUCT invents accessor function names for the
structure slots, for example. Some day, this will probably annoy me
enough that I'll write a DEFCLASS-like-DEFSTRUCT macro, but it hasn't
happened yet.)
-- [mdw]
> It's annoying that DEFCLASS is so much more verbose than DEFSTRUCT. I
> like the way that DEFSTRUCT invents accessor function names for the
> structure slots, for example. Some day, this will probably annoy me
> enough that I'll write a DEFCLASS-like-DEFSTRUCT macro, but it hasn't
> happened yet.)
Some years ago I used a macro "DEFCLASS*" which did that. Nowadays I
don't use it anymore. I think this is because I use objects often in a
functional way, i.e. most of their slots are very rarely modified.
Therefore, I do not want setters but only readers which I can specify in
the full DEFCLASS form. (In the rare cases where such slots are
modified I access them using SLOT-VALUE or WITH-SLOTS.)
Nicolas
> It's annoying that DEFCLASS is so much more verbose than DEFSTRUCT. I
> like the way that DEFSTRUCT invents accessor function names for the
> structure slots, for example. Some day, this will probably annoy me
> enough that I'll write a DEFCLASS-like-DEFSTRUCT macro, but it hasn't
> happened yet.)
You can use my DEFINE-CLASS macro, which is part of rg-utils:
http://flownet.com/ron/lisp/rg-utils.lisp
In the simplest case (which I use 99% of the time) it looks like this:
(define-class classname slot1 slot2 ...)
If you want to inherit:
(define-class (classname superclass1 superclass2 ...) slot1 slot2 ...)
And if you want to specify initial values or types for the slots:
(define-class classname (slot1 initial-value) ...)
(define-class classname (slot1 initial-value type) ...)
It automatically defines accessors name classname-slotX for each slot
and does a bunch of other handy things. For example:
? (define-class foo (x 1 integer))
#<STANDARD-CLASS FOO>
? (setf f (make-foo))
#<FOO #x302000E9CDED>
? (setf (foo-x f) 1.2)
Error: The value 1.2 can not be used to set the value of
the slot X in #<FOO #x302000E2518D>, because it is not
of type INTEGER.
rg
> http://flownet.com/ron/lisp/rg-utils.lisp
Shiny. I'll check that out. Thanks.
-- [mdw]
Regardless of the programming language, that's bad design to store
the various aspects of a single game-state in multiple toplevel
variables. Better to have a *single* toplevel variable which is
*THE*GAME*STATE* which contains all of *your* globals as slots.
Since the number of such slots in your game is fewer than a
thousand, association-lists are efficient enough, so that's what I
recommend. Perhaps include an extra keyword as first element before
the a-list proper. Thus:
(:GAME-STATE
(:PLAYER ...)
(:PLAYER-SHOTS ...)
(:ENEMIES ...)
(:ENEMY-SHOTS ...)
(:PARTICLES ...)
)
This is what I call a "NestAL" i.e. "Nested Association-List".
Thus if you find such a structure in some random place, and print
it out, the CAR reminds you what kind of object it's supposed to be.
You would of course have a constructor that created such a game state:
(defun make-initial-game-state () ...)
and you'd call it like this at the start of a new game:
(setq *game-state* (make-initial-game-state))
The advantage of keeping all of *your* globals as slots within an
associative map is that you can clone instances of this structure
and pass it around by a single handle, for example if you want to
bookmark a particular state in order to be able to return to it
later if the live game-state crashes due to bug in code you're
developing. By comparision, cloning the states of five different
globals and saving them in five different places, and remembering
where you them all, and not accidently mixing them up with pieces
from another saved game state, would be a royal pain. Also it's
easy to embed such a game state within a larger meta game, or
within a document that shows several interesting points in a famous
game, if you are ever so inclined.
In short: Whenever you have several pieces of a single concept,
such as a game state, they should be kept as slots (sub-objects)
within a single object, rather than as separate toplevel objects.
> So, I'd call it like:
> (remove-dead/stale-objects *player-shots*)
> (remove-dead/stale-objects *enemies*)
> (remove-dead/stale-objects *enemy-shots*)
> (remove-dead/stale-objects *player*)
> (remove-dead/stale-objects *particles*)
So much easier to say:
(loop for subnestal in (cdr *game-state*) do
(remove-dead/stale-objects (cdr subnestal)))
or even:
(loop for (tag . val) in (cdr *game-state*) do
(remove-dead/stale-objects val))
Note that either way will *not* support the complete expunging of
*all* obects within any one slot. If you need that, see next:
> remove-dead/stale-objects can ultimately result in ALL objects
> being removed and the special variable being set to nil.
In that case you have two good options:
- Re-write your clean-up function to take the entire sub-Nestal
(including the opening tag) to the clean-up function. If your
current clean-up function returns the new list, which might be
empty, all you need is a wrapper around it it:
(defun new-remove-dead/stale-objects (subnestal)
(rplacd subnestal (remove-dead/stale-objects (cdr subnestal))))
or equivalently (except for return value):
(defun new-remove-dead/stale-objects (subnestal)
(setf (cdr subnestal) (old-remove-dead/stale-objects (cdr subnestal))))
So then you'd write:
(loop for subnestal in (cdr *game-state*) do
(new-remove-dead/stale-objects subnestal))
- RPLACD the sub-nestal within the LOOP:
(loop for subnestal in (cdr *game-state*) do
(rplacd subnestal (remove-dead/stale-objects (cdr subnestal))))
or equivalently:
(loop for subnestal in (cdr *game-state*) do
(setf (cdr subnestal) (remove-dead/stale-objects (cdr subnestal))))
Your choice which of the good options to try.
> Or it could be initially called on a nil list.
That would not be a problem in any case, since you are only
removing items, never adding new items, and so given NIL start you
will always have NIL end. But if ever want to start from NIL and
*add* new items, it won't work easily with your original design,
but works nicely with any of my NestAL suggestions. For example:
(loop for subnestal in (cdr *game-state*) do
(setf (cdr subnestal) (add-new-objects subnestal)))
where it looks at the keyword in the CAR to know which slot it's
working with hence which objects to add in that particular list,
then updates the CDR i.e. the object-list of that (key . object-list)
item.
> The obvious solution to the reorganization is to either use CLOS,
> or to hold each group of objects in its own closure which knows
> how to remove dead objects from it internally. I'll move my stuff
> that way, but I'm interested in what you guys say about the mess
> which led me here. :)
That would also work, but IMO that would be overkill for your needs.
The main reason for OOP such as CLOS is if you have several
different types of objects that need to respond to the *same* set
of messages, but different types respond the same message by
different but analagous actions. For example, in a GUI, mouse click
may be passed to a window or a button or a checkbox etc., so all of
those would be classes that satisfy the "mouse clickable"
interface. Unless you have several *different* kinds of games that
you want to *all* respond to a single user-interface you've
defined, I don't see that you would get any added value from OOP/CLOS.
I know the above as a general rule. I was more interested in seeing
if I understood how to use lispbuilder-sdl and cl-opengl and had an
8 hour time limit to write whatever game I could in that time.
I well know the difference between a crappy hack and real code. :)
As as aside: I'm a long time and experienced professional hacker,
just not in Common Lisp--where I an a newbie. My approach to learning
Common Lisp was to be VERY careful about knowledge/idioms of other
languages creeping into my learning of lisp. I want to be completely
fluent in lisp and to not have an "accent" as it were. This means,
in some respects, I have to do stupid things occasionally. I'll only
make them a few times, internalize the right behavior as shown to me
by my betters, and continue to absorb/write lisp. Well, that's the
goal anyway.
> Better to have a *single* toplevel variable which is
> *THE*GAME*STATE* which contains all of *your* globals as slots.
Since I'm learning lisp, I decided to rewrite the game in CLOS so I
can learn CLOS.
> Since the number of such slots in your game is fewer than a
> thousand, association-lists are efficient enough, so that's what I
> recommend. Perhaps include an extra keyword as first element before
> the a-list proper. Thus:
> (:GAME-STATE
> (:PLAYER ...)
> (:PLAYER-SHOTS ...)
> (:ENEMIES ...)
> (:ENEMY-SHOTS ...)
> (:PARTICLES ...)
> )
> This is what I call a "NestAL" i.e. "Nested Association-List".
> Thus if you find such a structure in some random place, and print
> it out, the CAR reminds you what kind of object it's supposed to be.
I did something similar to
(defclass world ()
((players :initarg :players :initform nil :accessor world-players)
(player-shots ...)
(enemies ...)
(enemy-shots ...)
(particles ...))
(:documenatation "The world in which all drawable objects reside"))
> The advantage of keeping all of *your* globals as slots within an
> associative map is that you can clone instances of this structure
> and pass it around by a single handle, for example if you want to
> bookmark a particular state in order to be able to return to it
> later if the live game-state crashes due to bug in code you're
> developing.
Indeed! I hadn't thought if that directly, but I did wonder about
the very thing you just described. I'll have to test it out to see
how well that works out in practice.
I'm not sure yet how to clone object, under what conditions I want
to clone, and how shared structure works with cloning.
> In short: Whenever you have several pieces of a single concept,
> such as a game state, they should be kept as slots (sub-objects)
> within a single object, rather than as separate toplevel objects.
Noted.
Even though the above is obvious, I can't take it for granted that
this is how Common Lisp wants me to use it. :) Also, there are certain
kinds of lisp which use lots of global variables for things. I stumble
across it now and then, and haven't learned the distinctions yet for
when it is ok and when it isn't. Generally, when I've used global
variables, like in my CL-MW package, it was meant that way so I could
rebind them in later control flows, as opposed to just holding state
in them. That subtle distinction I can only learn through the practice
of writing and paying attention.
I appreciate the knowledge of those here to tell me what is important
or not when writing lisp.
Hrm, when I get to this portion of the code in my rewrite, I'll look
to see what I can use out of your recommendations.
>> The obvious solution to the reorganization is to either use CLOS,
>> or to hold each group of objects in its own closure which knows
>> how to remove dead objects from it internally. I'll move my stuff
>> that way, but I'm interested in what you guys say about the mess
>> which led me here. :)
>
> That would also work, but IMO that would be overkill for your needs.
> The main reason for OOP such as CLOS is if you have several
> different types of objects that need to respond to the *same* set
> of messages, but different types respond the same message by
> different but analagous actions.
It turns out I do have such needs. Some generic functions I have are:
step-entity -> perform a single simulation step for any object in the game.
render-entity -> render the entity at the calculated world location
think-entity -> Do AI calculations for bullets, different enemies, etc.
collide-entity -> Take any two objects (of whatever type) and see if collide.
An example of why it is nice is because since the primary method of
step-entity simply moves the object in the direction it is meant to
go, I can put a :before method around it for an enemy ship which can
think-entity, and adjust the dx dy for the ship based upon what it
thought. Then the less-specific step-entity method moves the ship
in the direction the ship just thought to go. Each type of enemy
(or bullets, or powerups) can think-entity with its own AI. Maybe the
powerup tries to move towards the player, or enemies circle around
the powerup defending it from the player, etc, etc, etc.
So, I think there is just enough usefulness here for me to explore in CLOS
while I wrote a small, practical, but real, example.
Thank you.
-pete
> I'm not sure yet how to clone object, under what conditions I want
> to clone, and how shared structure works with cloning.
There's no predefined object copying function, because the depth of
copying should depend on the objects.
http://www.nhplace.com/kent/PS/EQUAL.html
While I may try hard to be neutral to what I've learned before while
learning lisp, I'm mainly interested in the residue of experience
from my past. It's that residue where true skill is found.
Thanks for the Koan, though.
-pete
You need to learn the difference between hacks/crocks that are used
in some languages because those languages are very limited and you
just can't do the "right thing" there, vs. good programming
practice which is good regardless of the language.
Emulating data structures by using strings to hold the serialized
form of them, and using regular expressions to find sub-expressions
every time you need them, is a complete crock.
Using "containers" (Java jargon) to hold several values within a
single object, with a single handle on the container as a whole, is
good programming technique regardless of language.
Fortuately most programming languages nowadays have true
pointy-structures in which you can nest data, such as CONS trees in
Lisp, associative arrays in PHP, structures in C, and class Vector
in Java. Although data on disk files and in communication is
normally in serialized form, you can run a parser to convert
serialized form to pointy structures on input, work with pointy
structures internally, then serialize again to write back out to
files.
> I want to be completely fluent in lisp and to not have an
> "accent" as it were. This means, in some respects, I have to do
> stupid things occasionally. I'll only make them a few times,
> internalize the right behavior as shown to me by my betters, and
> continue to absorb/write lisp. Well, that's the goal anyway.
A smart person learn from others. A genius can do that and *also*
figure out some things for himself that nobody else ever figured
out before. Somewhere in that spectrum comes the ability to
recognize good technique that somebody showed you how to do using
one language, and figure out yourself how to do the same thing
using the primitives of a new language you are learning. For
example, in Lisp I invented NestALs (Nested association lists), and
it was easy to adapt that technique to nested associative arrays in
PHP.
> I'm not sure yet how to clone object, under what conditions I
> want to clone, and how shared structure works with cloning.
You define that yourself using the primitives that come with CLOS.
You can create a new instance of a CLOS class with any particular
slot values you desire. You can extract the slot values out of any
old CLOS-class instance you have around. Hence to do a shallow
clone (all slots *share* EQ values between old and new), all you
need to do is:
(defun shallow-copy-ship (ship)
(let ((sv1 (slot1-value ship))
(sv2 (slot2-value ship))
(sv3 (slot3-value ship)))
(make-ship sv1 sv2 sv3)))
If instead you want a deep copy of fixed depth, just nest such calls:
(defun depth2-copy-ship (ship)
(let ((specs (shallow-copy-ship-specs (ship-specs ship)))
(location (shallow-copy-ship-location (ship-location ship)))
(weapons (shallow-copy-ship-weapons (ship-weapons ship))))
(make-ship specs location weapons)))
If instead you want a deep copy all the way to leaves, you write a
recursive copy function that tests for leafness, together with a
generic function for shallow-copy of each class of object you might
encounter while traversing a deep structure, something like:
(defun deep-copy-object (object)
(if (is-leaf object) object
(obtype+slotsal-make-object
(object-type-of object)
(loop for slotname in (slotnames-of object)
collect (cons slotname
(deep-copy-object (object+slotname-get slotname object))))
)))
(defmethod shallow-copy (ship ...) ...)
(defmethod shallow-copy (ship-specs ...) ...)
(defmethod shallow-copy (ship-location ...) ...)
(defmethod shallow-copy (ship-weapons ...) ...)
*attention* None of the above code has been tested in any way.
> there are certain kinds of lisp which use lots of global
> variables for things.
Ideally if all those are relatively independent ideas, such as
*print-length* and *standard-output* and *readtable*, then they
should be separate globals, while if they are strongly related and
likely to be context-switched as a group, they should be slots
within a single global. But sometimes legacy design requires
keeping stuff that was wrong to begin with but the mistake was
realized too late to be corrected. But when you write *new*
software, feel free to use lots of hindsight of those mistakes to
do *your* software correctly now. And if you make a mistake in this
kind of design decision, feel free to refactor any time you can
afford the time and are sure the refactoring is a good idea. IMO
refactoring can be a *major* use of your time, not just when you've
made a mistake, but as a normal process. For example, you may use
PROG sequences at the start, the later refactor the code as nested
expressions and LOOP. You can use copy-and-paste to create multiple
similar pieces of code, then later refactor it as a function
definition, then maybe later refactor it as a macro definition. You
can define a function that does several things, then later realize
it's better to split the several things into separate functions so
that you can use those sections of code in different combinations
more easily. With modern text editors, such refactorings are easy
to do, and easy to avoid mistakes, so you don't need to be afraid
of refactoring.
For example, just the other day I refactored some PHP code that
used to explicitly build path+filename inside a couple places were
it was used, with duplicate string-concatenation code in the
various places, but now defines a function to do the path+filename
building which is simply called from several different places.
> Generally, when I've used global variables, like in my CL-MW
> package, it was meant that way so I could rebind them in later
> control flows, as opposed to just holding state in them.
If you rebind them as a group, that's a good reason to bundle them
as slots within a single structure bound on a single special
variable. If you rebind just one at a time, that's a good reason to
have them as separate special variables.
Note that you *can* bind and unbind slots within a single structure
by using a LET and a SETQ to bind and the finalization clause of an
UNWIND-PROTECT to restore the old value, but the syntax is awkward.
But you *can* write a macro to make the UNWIND-PROTECT syntax more
programmer-friendly, thus have your cake and eat it too:
(with-rebound-ship-weapons-slot (shipobject newweaponsvalue) ...body...)
==expandsTo=>
(let ((oldweaponsvalue (weapons-slot shipobject)))
(unwind-protect
(progn (setf (weapons-slot shipobject) newweaponsvalue)
...body...)
(setf (weapons-slot shipobject) oldweaponsvalue)
))
Exercise for reader to actually write the DEFMACRO that does that.
Remember that each of the two parameters shipobject and
newweaponsvalue must be evaluated just once in the correct
sequence, each bound to a GENSYMmed LET variable, then reference
the LET variables as many times as each is needed within the
body of the LET.
> >> The obvious solution to the reorganization is to either use CLOS,
> >> or to hold each group of objects in its own closure which knows
> >> how to remove dead objects from it internally. I'll move my stuff
> >> that way, but I'm interested in what you guys say about the mess
> >> which led me here. :)
> > That would also work, but IMO that would be overkill for your needs.
> > The main reason for OOP such as CLOS is if you have several
> > different types of objects that need to respond to the *same* set
> > of messages, but different types respond the same message by
> > different but analagous actions.
> It turns out I do have such needs. Some generic functions I have are:
> step-entity -> perform a single simulation step for any object in the game.
If all simulation steps are executed lock-step across multiple
object types, and if you have a master list of such steppable
objects that you can MAP or LOOP down, that would seem to work.
Or if you recursively traverse your main objects, to step those
objects and all sub-objects linked from them, but you *never* have
a single sub-object shared among more than one main object (to
avoid having the same sub-object encountered more than once during
the traversal hence stepped once per encounter hence stepped more
than once by mistake), that would work too.
Or if you have a global "step number" which is passed to the
step-object function, with a slot in each object telling the step
number when it was most recently stepped, so that if the shared
sub-object has already been stepped this time it won't be stepped a
second time yet, that would work.
> render-entity -> render the entity at the calculated world location
Ah, this is a typical OO GUI method. I agree CLOS would be appropriate.
> think-entity -> Do AI calculations for bullets, different enemies, etc.
I don't see how you'd want to think all the objects by a single
mapping operation. It makes more sense (to me) to think only the
toplevel objects, and think a different way the various kinds of
sub-objects. But if I'm mistaken then "nevermind".
> collide-entity -> Take any two objects (of whatever type) and see if collide.
OK, another good use for generic function acting on instances of CLOS classes.
> An example of why it is nice is because since the primary method of
> step-entity simply moves the object in the direction it is meant to
> go, I can put a :before method around it ...
OK, sounds like you need CLOS after all.
> So, I think there is just enough usefulness here for me to explore in CLOS
> while I wrote a small, practical, but real, example.
Agreed. Good luck.