A macro that defines functions whose names are based on macro arguments

* Note. Despite using StackOverflow frequently for a long time, this is the first question I posted on my own. Sorry if it is a little detailed. Constructive criticism was appreciated.

When I define a structure in Common Lisp using defstruct, a predicate function is automatically generated that checks whether its argument is a type defined by defstruct. For instance:

(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 do not seem to be generated by default (is there any way to specify this?), So I try to add this function myself because I would like: a) for this syntax to be consistent between structures and classes, b) have an abbreviation for (typep obj 'classname) , which I need to write very often and visually noisy, and c) as a programming exercise, since I'm still relatively new to Lisp.

I could write a macro that defines the predicate function given by the class name:

 (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 I need to pass to defun should look like 'classname-p . Here, where it’s hard for me. To create such a symbol, I could use the "symb" function from Paul Graham On Lisp (p. 58). When it starts on REPL:

 (symb 'book '-p) => BOOK-P 

My macro predicate gene looks like this:

 (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 character created (symb 'book '-p) is actually considered |'BOOK-P| implementation (SBCL), not BOOK-P . Of course, this now works:

 (let ((huck-finn (make-instance 'book))) (|'BOOK-P| huck-finn)) => True 

Why is a character created using the inter interned character as |'BOOK-P| ? In On Lisp (on the same page as above), Graham says: "Any line can be the print name of a character, even a string containing lowercase letters or macro characters, like parentheses. When a character name contains such oddities, it prints in vertical bars " In this case, there are no such oddities, is it? And do I correctly believe that the "name-print" of a character is what is actually displayed on the standard output when printing a character and, in the case of such oddities, differs from the shape of the character itself?

To write macros defining a function, such as gen-predicate , whose specific functions are named based on the arguments passed to the macro, it seems to me that Lisp hackers have probably been doing it for ages. User Kaz says here ( Combining characters with a total of lisp ) that you can often avoid "chopping up" characters, but this can defeat the purpose of this macro.

Finally, assuming that I could get the gen-predicate to work, how do I want it to be the best way to ensure that it is called for each new class as it is defined? Just as an initialize-instance can be configured to perform certain actions when creating an instance of a class, is there a generic defclass function that can perform actions when defining a class?

Thanks.

+6
source share
2 answers

This is a common problem: what is passed to the macro?

Compare these challenges:

 (symb 'book '-p) 

and

 (symb ''book '-p) 

Your macroform is as follows:

 (gen-predicate 'book) 

GEN-PREDICATE - macro. classname is the parameter for this macro.

Now what is the value of classname inside the macro during code extension? Is it a book or a 'book ?

This is actually the last one because you wrote (gen-predicate 'book) . Remember: macros see the source code, and the source of the argument is passed to the macro function, not the value. Argument 'book . So it goes. (QUOTE BOOK) the same thing, only in different ways. So this is a list of two items. The first element is the QUOTE symbol, and the second element is the book symbol.

Thus, the macro now calls the SYMB function with the argument value (QUOTE BOOK) or, in short, 'book .

If you want to generate a predicate without the quotation mark, you need to write:

 (gen-predicate book) 

Alternatively, you can also change the macro:

 (symb classname '-p) 

:

 (symbol (if (and (consp classname) (eq (first classname) 'quote)) (second classname) classname)) 

Comparison

We write

 (defun foo () 'bar) 

but not

 (defun 'foo () 'bar) ; note the quoted FOO 

DEFUN is a macro, and the first argument is the name of the function. This is a similar problem then ...

Second part of the question

In fact, I do not know a good answer. I cannot remember any simple way to run the code (for example, to define a function) after the class is defined.

  • Maybe use a MOP, but it's ugly.

  • write a custom DEFINE-CLASS macro that does what you want: extends to DEFCLASS and DEFUN .

  • iterate over all characters in a package, find classes and determine the corresponding predicates

+5
source

To solve the second part of the question, classes themselves are objects, thanks to MOP, so it would be possible to write the following method: after method for initialize-instance specialized in STANDARD-CLASS. But you should check the MOP to determine if the definition of such a method is allowed or not.

If possible, then yes, you can run the code in response to creating the class; however, since you do not know the name of the class created before execution, you cannot pronounce it in text form in the source, so you cannot use your macro (if you are not using eval). Would you rather use something like

 (let ((classname (class-name class))) (compile (generate-my-predicate-symbol classname) (lambda (x) (typep x classname)))) 

I think Rainer's suggestion to write your own DEFINE-CLASS macro is the way, I mean, the way an experienced Lispper is likely to do this if there are no other considerations in the game. But I'm not a very experienced Lispper, so I could be wrong;)

+1
source

Source: https://habr.com/ru/post/987796/


All Articles