Hi all,
I want to use one of the techniques presented in AMOP (Chapter 3,
extending the language)
On Page 83, section #3.5, the authors illustrated how to add extra
functionality to a slot of a class.
By implementing a meta object attribute-class and adding a new slot arg
:attributes, you can do something like this:
(defclass monitored-credit-rating (credit-rating)
((level :attributes (last-checked interval)))
(:metaclass attribute-class))
(setq cr (make-instance 'credit-rating))
(setf (slot-attribute cr 'level 'date-set) "12/15/90")
(slot-attribute cr 'level 'date-set) => "12/15/90"
The implementation is straightforward. First define a custom meta
object
(defclass attributes-class (standard-class) ())
Then, you can specialize on 'compute-effective-slot-definition and
'compute-slots so that there will be an extra 'all-attributes slot
created for each object to hold the slot-attribute properties.
The only reminding work is to find a way so that defclass macro will
accept the extra slot arg :attributes.
CL-USER 1 > (defclass monitored-credit-rating (credit-rating)
((level :attributes (last-checked interval)))
(:metaclass attribute-class))
Error: MAKE-INSTANCE is called with unknown keyword :ATTRIBUTES among
the arguments (STANDARD-DIRECT-SLOT-DEFINITION :NAME LEVEL :ATTRIBUTES
(LAST-CHECKED INTERVAL)) which is not one of (:DOCUMENTATION :FLAGS
:INITARGS :ALLOCATION :TYPE :INITFUNCTION :INITFORM :NAME :WRITERS
:READERS).
On page 95, there's a reference implementation of defclass, which
calls a generic function canonicalize-defclass-option so that a
specialized attributes-class can process the :attributes arg.
But canonicalize-defclass-option is not part of MOP. So the only option
that I can think of is to write another defclass* to bypass defclass
and mess with ensure-class directly.
Is there an alternative to this? If I want to add another feature to
the slot, I'd have to write another macro, defclass**, but then I
cannot use defclass* and defclass** at the same time.
Thanks,
-- Mac
mac wrote:
> Hi all,
>
> I want to use one of the techniques presented in AMOP (Chapter 3,
> extending the language)
>
> On Page 83, section #3.5, the authors illustrated how to add extra
> functionality to a slot of a class.
>
> By implementing a meta object attribute-class and adding a new slot arg
> :attributes, you can do something like this:
>
> (defclass monitored-credit-rating (credit-rating)
> ((level :attributes (last-checked interval)))
> (:metaclass attribute-class))
>
> (setq cr (make-instance 'credit-rating))
> (setf (slot-attribute cr 'level 'date-set) "12/15/90")
>
> (slot-attribute cr 'level 'date-set) => "12/15/90"
>
>
> The implementation is straightforward. First define a custom meta
> object
>
> (defclass attributes-class (standard-class) ())
>
> Then, you can specialize on 'compute-effective-slot-definition and
> 'compute-slots so that there will be an extra 'all-attributes slot
> created for each object to hold the slot-attribute properties.
>
> The only reminding work is to find a way so that defclass macro will
> accept the extra slot arg :attributes.
>
> CL-USER 1 > (defclass monitored-credit-rating (credit-rating)
> ((level :attributes (last-checked interval)))
> (:metaclass attribute-class))
>
> Error: MAKE-INSTANCE is called with unknown keyword :ATTRIBUTES among
> the arguments (STANDARD-DIRECT-SLOT-DEFINITION :NAME LEVEL :ATTRIBUTES
> (LAST-CHECKED INTERVAL)) which is not one of (:DOCUMENTATION :FLAGS
> :INITARGS :ALLOCATION :TYPE :INITFUNCTION :INITFORM :NAME :WRITERS
> :READERS).
>
>
> On page 95, there's a reference implementation of defclass, which
> calls a generic function canonicalize-defclass-option so that a
> specialized attributes-class can process the :attributes arg.
>
> But canonicalize-defclass-option is not part of MOP. So the only option
> that I can think of is to write another defclass* to bypass defclass
> and mess with ensure-class directly.
>
> Is there an alternative to this?
Yes, define a new direct slot definition class, roughly like this:
(defclass attribute-direct-slot-definition
(standard-direct-slot-definition)
((attributes :initarg :attributes)))
...and make sure that it is used by your metaclass:
(defmethod direct-slot-definition-class
((class attribute-class) &rest initargs)
(declare (ignore initargs))
(find-class 'attribute-direct-slot-definition))
Then the standard ANSI defclass form will accept your new :attributes
for all slots for that metaclass. You can access those attributes in
compute-effective-slot-definition, but note that they are not
automatically copied to the resulting effective slot definition - you
have to do this yourself.
I hope this helps,
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
mac wrote:
> Thanks Pascal, that's definitely helpful.
>
> I tried the following (slightly modifying the compute-slots as
> presented in AMOP p.88).
>
> When I evaluated the defclass form, lispworks and sbcl gave (different)
> error message.
>
> SBCL complains that meta-classes #<STANDARD-CLASS STANDARD-CLASS> and
> #<STANDARD-CLASS ATTRIBUTES-CLASS> are incompatible. But as defined in
> the code, ATTRIBUTES-CLASS is a subclass of STANDARD-CLASS.
That's not sufficient, you also have to define a silly method on
validate-superclass, like this:
(defmethod validate-superclass
((class attribute-class) (superclass standard-class))
t)
> Lispworks still gives the same error as I posted in the original
> message.
Maybe try the following:
(defmethod direct-slot-definition-class
((class attribute-class) &key &allow-other-keys)
(find-class 'attribute-direct-slot-definition))
It shouldn't make a difference compared to using &rest initargs, but
maybe it does...
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/
So finally I get this working with sbcl. I'm still confused with the
relationship between direct and effective slots and what does it buy
for the user of MOP. I know there must be a reason but I haven't
studied hard enough.
The sbcl source code helps a lot. Without that it'll be very hard to
figure this out.
Just a side note though, what tools do most lispers use when reading
the source code and figure things out?
For me, I go to the definition of the defclass macro, and see what it
does. But since there are so many functions / macro it uses, I pick the
one that is likely to show me what I need to and then set a trace on
them.
(trace compute-effective-slot-definition-initargs)
(trace canonize-defclass-slots)
(trace std-compute-slots)
(trace make-instance)
BTW, is there a way to link the symbols in sbcl core to the source code
on my disk so that slime-edit-definition will jump to the source file?
It's much better than using etags IMO.
Anyway, thanks for your help. I still have to factor the code so it
works on all lisp implementions that I use (and at the same time brush
up my MOP knowledge).
(in-package :cl-user)
(use-package :sb-mop)
(defclass attributes-class (standard-class) ())
(defmethod sb-pcl:validate-superclass
((class attributes-class) (superclass sb-mop:standard-class))
t)
(defclass attribute-direct-slot-definition
(sb-pcl:standard-direct-slot-definition)
((attributes :initarg :attributes :initform nil
:accessor slot-definition-attributes)))
(defmethod sb-pcl:direct-slot-definition-class
((class attributes-class) &key &allow-other-keys)
(find-class 'attribute-direct-slot-definition))
(defclass attribute-effective-slot-definition
(sb-pcl:standard-effective-slot-definition)
((attributes :initarg :attributes :initform nil
:accessor slot-definition-attributes)))
(defmethod sb-pcl:effective-slot-definition-class
((class attributes-class) &key &allow-other-keys)
(find-class 'attribute-effective-slot-definition))
(defmethod sb-pcl::compute-effective-slot-definition-initargs
((class attributes-class) direct-slotds)
(let* ((attributes nil))
(dolist (slotd direct-slotds)
(when slotd
(unless attributes
(setq attributes (slot-definition-attributes slotd)))))
(append (call-next-method) (list :attributes attributes))))
(defmethod sb-pcl:compute-slots ((class attributes-class))
(let* ((normal-slots (call-next-method))
(alist
(mapcar
#'(lambda (slot)
(cons (sb-pcl:slot-definition-name slot)
(mapcar #'(lambda (attr) (cons attr nil))
(slot-definition-attributes slot))))
normal-slots)))
(cons (make-instance 'sb-pcl:standard-effective-slot-definition
:name 'all-attributes
:initform `',alist
:initfunction #'(lambda () alist))
normal-slots)))
(defun slot-attribute (instance slot-name attribute)
(cdr (slot-attribute-bucket instance slot-name attribute)))
(defun (setf slot-attribute) (new-value instance slot-name attribute)
(setf (cdr (slot-attribute-bucket instance slot-name attribute))
new-value))
(defun slot-attribute-bucket (instance slot-name attribute)
(let* ((all-buckets (slot-value instance 'all-attributes))
(slot-bucket (assoc slot-name all-buckets)))
(unless slot-bucket
(error "The slot named ~S of ~S has no attributes."
slot-name instance))
(let ((attr-bucket (assoc attribute (cdr slot-bucket))))
(unless attr-bucket
(error "The slot named ~S of ~S has no attribute named ~S."
slot-name instance attribute))
attr-bucket)))
(defclass credit-rating ()
((level :initform 1 :initarg :level :attributes (date-set time-set)))
(:metaclass attributes-class))
(defparameter *credit-rating* (make-instance 'credit-rating))
(slot-value *credit-rating* 'all-attributes)
(slot-attribute *credit-rating* 'level 'date-set)
(setf (slot-attribute *credit-rating* 'level 'date-set) "12/15/90")
mac wrote:
> So finally I get this working with sbcl. I'm still confused with the
> relationship between direct and effective slots and what does it buy
> for the user of MOP. I know there must be a reason but I haven't
> studied hard enough.
Join the club. I hate doc, so doing MOP was a bitch. Are you reading
AMOP? On-line I think.
To answer your Q, do not forget that a slot named XXX can be listed in
several defclasses in an inheritance tree. Each appearance produces a
direct slot defintion (DSD) to make /that/ defclass happy. (Pardon the
tech jargon.) Now you call make-instance on an actual class way down the
inheritance tree and CLOS has to make a real live slot. So now it needs
to take all the relevant DSDs and come up with a working (aka
"effective") slot definition. ESD. compute-esd is your shot to customize
how DSDs get cooked into one ESD.
hth, ken
mac <········@gmail.com> wrote:
> BTW, is there a way to link the symbols in sbcl core to the source code
> on my disk so that slime-edit-definition will jump to the source file?
> It's much better than using etags IMO.
Yes, that's controlled by the SYS logical pathname host
(http://www.sbcl.org/manual/Lisp-Pathnames.html). For example:
(push '("SYS:SRC;**;*.*.*" #P"/path/to/the/source/src/**/*.*")
(logical-pathname-translations "SYS"))
(push '("SYS:CONTRIB;**;*.*.*" #P"/path/to/the/source/contrib/**/*.*")
(logical-pathname-translations "SYS"))
Alternatively you can build SBCL from source, which should set the
default translations up correctly for your installation.
--
Juho Snellman
mac wrote:
> Anyway, thanks for your help. I still have to factor the code so it
> works on all lisp implementions that I use (and at the same time brush
> up my MOP knowledge).
Writing portable MOP code isn't that easy because all implementations
deviate in one way or the other. My Closer to MOP compatibility layer
helps a little in that regard, so you may want to consider using it.
Pascal
--
3rd European Lisp Workshop
July 3-4 - Nantes, France - co-located with ECOOP 2006
http://lisp-ecoop06.bknr.net/