*Note: Despite having frequented StackOverflow for a long time, this is the first question that I have posted myself. Apologies if it's a bit verbose. Constructive criticism appreciated.
When I define a struct in Common Lisp using defstruct, a predicate function is automatically generated that tests whether its argument is of the type defined by the defstruct. Eg:
(defstruct book
title
author)
(let ((huck-finn (make-book :title "The Adventures of Huckleberry Finn" :author "Mark Twain")))
(book-p huck-finn))
=> True
However, when defining a class using defclass, such functions are seemingly not generated by default (is there a way to specify this?), so I'm trying to add this functionality myself, because I'd like a) for this syntax to be consistent between structs and classes, b) to have an abbreviation of (typep obj 'classname)
, which I need to write very often and is visually noisy,
and c) as a programming exercise, since I'm still relatively new to Lisp.
I could write a macro that defines a predicate function given the name of a class:
(defclass book ()
((title :initarg :title
:accessor title)
(author :initarg :author
:accessor author)))
;This...
(defmacro gen-predicate (classname)
...)
;...should expand to this...
(defun book-p (obj)
(typep obj 'book))
;...when called like this:
(gen-predicate 'book)
The name that I need to pass to defun must be of the form 'classname-p
. Here's where I have difficulty. To create such a symbol, I could use the "symb" function from Paul Graham's On Lisp (p. 58). When it is run on the REPL:
(symb 'book '-p)
=> BOOK-P
My gen-predicate macro looks like this so far:
(defmacro gen-predicate (classname)
`(defun ,(symb classname '-p) (obj)
(typep obj ,classname)))
(macroexpand `(gen-predicate 'book))
=>
(PROGN
(EVAL-WHEN (:COMPILE-TOPLEVEL) (SB-C:%COMPILER-DEFUN '|'BOOK-P| 'NIL T))
(SB-IMPL::%DEFUN '|'BOOK-P|
(SB-INT:NAMED-LAMBDA |'BOOK-P|
(OBJ)
(BLOCK |'BOOK-P| (TYPEP OBJ 'BOOK)))
NIL 'NIL (SB-C:SOURCE-LOCATION)))
T
It would seem that the symbol created by (symb 'book '-p)
is actually considered |'BOOK-P|
by the implementation (SBCL), not BOOK-P
. Sure enough, this now works:
(let ((huck-finn (make-instance 'book)))
(|'BOOK-P| huck-finn))
=> True
Why is the symbol created by symb interned as |'BOOK-P|
? In On Lisp (same page as above) Graham says: "Any string can be the print-name of a symbol, even a string containing lowercase letters or macro characters like parentheses. When a symbol's name contains such oddities, it is printed within vertical bars." No such oddities exist in this case, do they? And am I correct in thinking that the "print-name" of a symbol is what is actually displayed on the standard output when the symbol is printed, and is, in the case of such oddities, distinct from the form of the symbol itself?
To be able to write function-defining macros like gen-predicate
- whose defined functions are named based on the arguments passed to the macro - seems to me like something that Lisp hackers have probably been doing for ages. User Kaz says here (Merging symbols in common lisp) that the "mashing-up" of symbols can often be avoided, but that would defeat the purpose of this macro.
Finally, assuming I could get gen-predicate
to work how I want, what would be the best way of ensuring that it be called for each new class as they are defined? Much in the same way as initialize-instance
can be customized to perform certain actions upon instantiation of a class, is there a generic function called by defclass that can perform actions upon definition of a class?
Thank you.
'book
is really(quote book)
. I understand your second solution, but the first is confusing. I had actually already realised that (macroexpand(gen-predicate book) expands into what I wanted, but since the symbol
book` is not quoted, then it is interpreted as a variable, which is undefined. I didn't know how to avoid that problem. Thank you for your prompt and helpful reply! – Helminthiasis