Any Hints on Saving an Invocation of a Macro/Function?

common-lisp, lisp

Solution

If you define your animal classes with a macro, then you can let the macro record the source code.

Example (works in LispWorks):

We can store the source code for example in a class allocated slot using an alist. Alternatively you could just use a simple global variable.

(defclass animal ()
  ((source :allocation :class :initform nil)))

Above has a slot which should point to an alist of class name and source code.

(defmacro def-animal-class (&whole code name feeding )
  `(progn
     (defclass ,name (animal) ())
     (defmethod feed ((animal ,name)) ,@feeding)
     (let ((item (assoc ',name
                        (slot-value (class-prototype (find-class 'animal))
                                    'source))))
       (if item
           (setf (cdr item) ',code)
         (setf (slot-value (class-prototype (find-class 'animal))
                           'source)
               (list (cons ',name ',code)))))
     ',name))

What does the generated code look like?

CL-USER > (pprint (macroexpand-1 '(def-animal-class cat ((print "feeding a cat")))))

(PROGN
  (DEFCLASS CAT (ANIMAL) NIL)
  (DEFMETHOD FEED ((ANIMAL CAT)) (PRINT "feeding a cat"))
  (LET ((ITEM (ASSOC 'CAT (SLOT-VALUE (CLASS-PROTOTYPE (FIND-CLASS 'ANIMAL))
                                      'SOURCE))))
    (IF ITEM
        (SETF (CDR ITEM) '(DEF-ANIMAL-CLASS CAT ((PRINT "feeding a cat"))))
      (SETF (SLOT-VALUE (CLASS-PROTOTYPE (FIND-CLASS 'ANIMAL)) 'SOURCE)
            (LIST (CONS 'CAT '(DEF-ANIMAL-CLASS CAT ((PRINT "feeding a cat"))))))))
  'CAT)

Using it:

CL-USER 75 > (def-animal-class cat ((print "feeding a cat some more")))
CAT

CL-USER 76 > (cdr (first (slot-value (class-prototype (find-class 'animal))
                                     'source)))

(DEF-ANIMAL-CLASS CAT ((PRINT "feeding a cat some more")))

Thus the last source code gets recorded, whenever one uses the macro `DEF-ANIMAL-CLASS`. You can then for example write the code to a file:

(with-open-file (s "~/animals.sexp" :direction :output :if-exists :supersede)
  (pprint (slot-value (class-prototype (find-class 'animal)) 'source) s))

A simple `READ` brings it back.

Problem

A while ago, for a little zoo-based example, I wrote a base class ANIMAL, some sub-classes CAT, MOUSE, etc. a generic method FEED taking an ANIMAL parameter and some methods specialised on each ANIMAL sub-type. After writing the second and third class, method pair I realised I was writing same thing over and over and decided to write a macro DEF-ANIMAL-SUBCLASS that expanded into a PROGN that defined the new sub-class and the appropriate method. I then realised that I had just given my users a way of defining their own ANIMAL sub-types, something they might find useful! However, while they might just do that in a running image, I didn't have a way of saving their new ANIMAL type so that, in the event that the image was restarted, any new ANIMAL type would be re-created for them (without them having to re-evaluate the macro). Is there a conventional way of doing this? Is it something that should not be done? Any hints would be gratefully received! Cheers, P

Original source