Antsan
10/12/2015 9:17:00 PM
Am Montag, 12. Oktober 2015 21:55:26 UTC+2 schrieb informatimago:
> Antsan <thomas.bartscher@gmail.com> writes:
>
> > I'm trying to formalize a PnP RPG by implementing its rules in Common Lisp. For
> > this I need an actor object. Every actor object has the same attributes and I
> > wanted easy access to those attributes by name without having to hardcode
> > everything.
> > What I came up with was this (I'm using fset):
> >
> >
> > ;;;;; attributes.lisp
> > (in-package #:skyscrapers)
> >
> > (defparameter *attributes*
> > (empty-map))
> >
> > (defmacro add-attr (name counter)
> > (declare (type symbol name))
> > `(setf *attributes*
> > (with (with *attributes*
> > ',name ',counter)
> > ',counter ',name)))
> >
> > (defmacro add-attrs (&body names)
> > `(progn
> > ,@(loop for (name counter)
> > on names by #'cddr
> > collect
> > `(add-attr ,name ,counter))))
> >
> > (add-attrs
> > strength intelligence
> > resistance dexterity
> > empathy fortitude
> > charisma secrecy
> > )
> >
> > ;;;; actors.lisp
> > (in-package #:skyscrapers)
> >
> > #.(let ((attrs (mapcar (lambda (attr)
> > `(,attr .
> > ,(gensym (string-downcase (symbol-name attr)))))
> > (convert 'list
> > (domain *attributes*)))))
> > `(progn
> > (defclass actor ()
> > ((,(gensym "exhaustion")
> > :type (integer 0)
> > :initarg :exhaustion
> > :initform 0
> > :reader exhaustion)
> > ,@(mapcar (lambda (attr/sym)
> > `(,(cdr attr/sym)
> > :type (integer 1)
> > :initarg ,(intern (symbol-name (car attr/sym))
> > :keyword)
> > :initform 1))
> > attrs)))
> > (defgeneric get-attr (actor attr))
> > ,@(mapcar (lambda (attr/sym)
> > `(defmethod get-attr ((actor actor)
> > (attr (eql ',(car attr/sym))))
> > (slot-value actor ',(cdr attr/sym))))
> > attrs)))
> >
> > This way I don't pollute my function namespace with accessors, which has been a
> > problem in a similar project before. Another boon is that I now can use GET-ATTR
> > safely with perfect knowledge that I'm not reading something which is not an
> > attribute. Using a function which expects an attribute and supplying EXHAUSTION
> > won't work as it would if I'd be using SLOT-VALUE.
> >
> > But I can't help but think that this is ugly. I'm not quite sure why, though.
> > Does anybody see something wrong with what I did here?
>
> I don't see any justification for using a global variable to hold the
> atribute of a single actor class.
The global variable has another purpose regarding the rules. It maps attributes
to their opposite.
If I coded this by hand I'd need to write down every attribute twice, once when
defining opposites and once for defining the actor class.
> At the very least, you could write a single macro
>
> (define-class-with-attributes actor
> strength intelligence
> resistance dexterity
> empathy fortitude
> charisma secrecy)
>
>
> But I don't see either any worthwhile justification for using get-attr
> instead of slot-value. You can still define it as a "functional
> abstration", but since you don't signal any more specific error than
> slot-value, you could just define it as:
>
> (defun get-attribute (actor attribute) (slot-value actor attribute))
That wouldn't throw an error on
(slot-value actor 'exhaustion)
or whichever other slots not representing attributes I happen to add to the
class.
But moving the whole thing into a macro makes sense.
#.`(define-class-with-hidden actor ()
((exhaustion ...))
(get-attr ,@(convert 'list (domain *attributes*))))
More generally the intent of using something other than slot-value is to mark
slots with their purpose.