Reputation: 177
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
Upvotes: 3
Views: 196
Reputation: 139311
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.
Upvotes: 7
Reputation: 14291
Common Lisp is an image-based language, so, besides the solutions given in Inaimathi's answer, you could also just save the image, and all user-defined classes (along with other state, with the exception of ephemeral things like network connections etc.) will be there if you restart it.
How to do this depends on your CL implementation, so you'll have to check its documentation. CCL uses ccl:save application
, SBCL sb-ext:save-lisp-and-die
, CLISP ext:saveinitmem
, and so on.
Which of these methods (one of those suggested by Inaimathi or saving an image) to choose depends, of course, on your application and needs, as each of them have different advantages and drawbacks.
Upvotes: 3
Reputation: 14065
The conventional way of doing this is to use a database to store your animal subclasses. Pick one, hook up CLSQL and have it store animal records in a format that you can interpret back into their respective definitions.
Depending on the scale and deployment situation, you might also get away with just handling it in a flat file.
That is, in addition to defining a new subclass and methods, have your def-animal-subclass
serialize their def...
statements into a separate .lisp
file. Your program would then load that file at the point it handles its configuration. (Do make sure to think it through in a bit of detail though. For instance, what happens if your user defines an animal subclass that already exists?) Take a look at how Emacs stores customizations for some ideas.
Upvotes: 5