3

I was toying around with macros and clos, where I created an "object" macro to create instances

(defmacro object (class &rest args) `(make-instance ',class ,@args)) 

Now doing this, I also ended up kind of wanting to do something similar for accessor functions created by clos. Example:

(defclass person () ((name :accessor person-name :initarg :name))) 

then creating the instance

(setf p1 (object person :name "tom")) 

now to get the name from the object obviously I would call person-name, however just as with the object macro, I wanted to create a "gets" macro to do this. So ideally:

(gets person name p1) which then would return the name. 

The problem then is the binding of person and name (person-name) and how to do that. Is there anyway to get those two arguments bound together in the macro? sort of like:

(defmacro gets (class var object) `(,class-,var ,object)) 
2
  • 3
    can you explain WHY you actually created a less useful object macro? Doesn't make any sense to me. The gets macro also does not make any sense, IMHO. Commented Jun 26, 2014 at 18:34
  • 1
    As stated in the other answers comment fields, I was testing out macro's nothing more or less. The "object" macro was to give more of a - java-ish feel to creating classes for the sake of showing to a friend of mine that love java's syntaxes. So once again, this isn't, never was, never was going to be, and never will be used for anything, except 100% in a process of learning & exploring how stuff work. Commented Jul 6, 2014 at 20:07

4 Answers 4

11

I think I may have misunderstood the original intent. At first I thought you were asking how to generate the accessor names for the class definition, which third part of the answer addresses. After reading through a second time, it actually sounds like you want to generate a new symbol and call it with some argument. That's easy enough too, and is given in the second part of this answer. Both the second and third parts depend on being able to create a symbol with a name that's built from the names of other symbols, and that's what we start with.

"Concatenating" symbols

Each symbol has a name (a string) that you can obtain with symbol-name. You can use concatenate to create a new string from some old strings, and then use intern to get a symbol with the new name.

(intern (concatenate 'string (symbol-name 'person) "-" (symbol-name 'name))) ;=> PERSON-NAME 

Reconstructing an accessor name

(defmacro gets (class-name slot-name object) (let ((accessor-name (intern (concatenate 'string (symbol-name class-name) "-" (symbol-name slot-name)) (symbol-package class-name)))) `(,accessor-name ,object))) 
(macroexpand-1 '(gets person name some-person)) ;=> (PERSON-NAME SOME-PERSON) 

For a number of reasons, though, this isn't very robust. (i) You don't know whether or not the slot has an accessor of the form <class-name>-<slot-name>. (ii) Even if the slot does have an accessor of the form <class-name>-<slot-name>, you don't know what package it's in. In the code above, I made the reasonable assumption that it's the same as the package of the class name, but that's not at all required. You could have, for instance:

(defclass a:person () ((b:name :accessor c:person-name))) 

and then this approach wouldn't work at all. (iii) This doesn't work with inheritance very well. If you subclass person, say with north-american-person, then you can still call person-name with a north-american-person, but you can't call north-american-person-name with anything. (iv) This seems to be reïnventing slot-value. You can already access the value of a slot using the name of the slot alone with (slot-value object slot-name), and I don't see any reason that your gets macro shouldn't just expand to that. There you wouldn't have to worry about the particular name of the accessor (if it even has one), or the package of the class name, but just the actual name of the slot.

Generating accessor names

You just need to extract the names of the symbols and to generate a new symbol with the desired name.
If you want to automatically generate accessors with defstruct style names, you can do it like this:

(defmacro define-class (name direct-superclasses slots &rest options) (flet ((%slot (slot) (destructuring-bind (slot-name &rest options) (if (listp slot) slot (list slot)) `(,slot-name ,@options :accessor ,(intern (concatenate 'string (symbol-name name) "-" (symbol-name slot-name))))))) `(defclass ,name ,direct-superclasses ,(mapcar #'%slot slots) ,@options))) 

You can check that this produces the kind of code that you'd expect by looking at the macroexpansion:

(pprint (macroexpand-1 '(define-class person () ((name :type string :initarg :name) (age :type integer :initarg :age) home)))) (DEFCLASS PERSON NIL ((NAME :TYPE STRING :INITARG :NAME :ACCESSOR PERSON-NAME) (AGE :TYPE INTEGER :INITARG :AGE :ACCESSOR PERSON-AGE) (HOME :ACCESSOR PERSON-HOME))) 

And we can see that it works as expected:

(define-class person () ((name :type string :initarg :name) (age :type integer :initarg :age) home)) (person-name (make-instance 'person :name "John")) ;=> "John" 

Other comments on your code

(defmacro object (class &rest args) `(make-instance ',class ,@args)) 

As Rainer pointed out this isn't very useful. For most cases, it's the same as

(defun object (class &rest args) (apply 'make-instance class args)) 

except that you can (funcall #'object …) and (apply #'object …) with the function, but you can't with the macro.

Your gets macro isn't really any more useful than slot-value, which takes an object and the name of a slot. It doesn't require the name of the class, and it will work even if the class doesn't have a reader or accessor.

Don't (naïvely) create symbol names with format

I've been creating symbol names with concatenate and symbol-name. Sometimes you'll see people use format to construct the names, e.g., (format nil "~A-~A" 'person 'name), but that's prone to issues with capitalization settings that can be changed. For instance, in the following, we define a function foo-bar, and note that the format based approach fails, but the concatenate based approach works.

CL-USER> (defun foo-bar () (print 'hello)) FOO-BAR CL-USER> (foo-bar) HELLO HELLO CL-USER> (setf *print-case* :capitalize) :Capitalize CL-USER> (funcall (intern (concatenate 'string (symbol-name 'foo) "-" (symbol-name 'bar)))) Hello Hello CL-USER> (format nil "~a-~a" 'foo 'bar) "Foo-Bar" CL-USER> (intern (format nil "~a-~a" 'foo 'bar)) |Foo-Bar| Nil CL-USER> (funcall (intern (format nil "~a-~a" 'foo 'bar))) ; Evaluation aborted on #<Undefined-Function Foo-Bar {1002BF8AF1}>. 

The issue here is that we're not preserving the case of the symbol names of the arguments. To preserve the case, we need to explicitly extract the symbol names, rather than letting the print functions map the symbol name to some other string. To illustrate the problem, consider:

CL-USER> (setf (readtable-case *readtable*) :preserve) PRESERVE ;; The symbol-names of foo and bar are "foo" and "bar", but ;; you're upcasing them, so you end up with the name "FOO-BAR". CL-USER> (FORMAT NIL "~{~A~^-~}" (MAPCAR 'STRING-UPCASE '(foo bar))) "FOO-BAR" ;; If you just concatenate their symbol-names, though, you ;; end up with "foo-bar". CL-USER> (CONCATENATE 'STRING (SYMBOL-NAME 'foo) "-" (SYMBOL-NAME 'bar)) "foo-bar" ;; You can map symbol-name instead of string-upcase, though, and ;; then you'll get the desired result, "foo-bar" CL-USER> (FORMAT NIL "~{~A~^-~}" (MAPCAR 'SYMBOL-NAME '(foo bar))) "foo-bar" 
Sign up to request clarification or add additional context in comments.

1 Comment

This is exactly what I was looking for(and allot more) thanks. I am quite aware of it not being very usefull in any serious program, but it is mostly just to see the possibilities of macros and learning to actually use them. The rests of it pretty much went over my head but I'll be sure to revisit it when I'm more used to macros.
0

This function creates symbols from string designators:

(defun symb (&rest args) (intern (format nil "~{~a~^-~}" (mapcar #'string args)))) 

The function uses format, yet passes Joshua's test:

CL-USER> (symb 'foo :bar "BAZ") FOO-BAR-BAZ NIL CL-USER> (defun foo-bar () (print 'hello)) FOO-BAR CL-USER> (foo-bar) HELLO HELLO CL-USER> (setf *print-case* :capitalize) :Capitalize CL-USER> (funcall (symb 'foo 'bar)) Hello Hello 

4 Comments

You're no longer getting a symbol whose name is formed from the symbol names of the input arguments. E.g., if you call (symb '|foo| '|bar|), you'll get back a symbol named "FOO-BAR", not a symbol named "foo-bar". If you want to use format, just map symbol-name over the input symbols (or string over the arguments). The important thing is preserving the case in the symbol names.
@Joshua, Indeed, I've fixed my function. Thanks!
I also updated my answer with an example of how, e.g., the readtable case could lead to a problem with the string-upcase approach.
"from strings, symbols and/or keywords" This is a bit too specific, and a little inaccurate. string-upcase (from the first version) and string both take a string designator, which is a string, symbol (possibly a keyword, which is just a symbol in the keyword package), or a character. So you can do, for instance, (symb 'foo #\- :bar).
0

If you want your gets to use accessor methods:

(defmacro gets (class var object) `(,(intern (format nil "~a-~a" (symbol-name class) (symbol-name var))) ,object)) 

In general, what you're trying to accomplish is not really useful. make-instance is a well known symbol, easily greppable, part of the standard and optimized by some implementations when the class name is constant. So with your object macro, you're just saving a few characters and a single-quote. Usually, one hides make-instance in specific cases where you don't want to provide a direct way to initialize instances, or more likely, when you want to provide layers of initialization (e.g. phases of initialization, Lisp slots and foreign objects).


PS: I remember vaguely that someone prominent in the standardization of Common Lisp argued in favor of always wrapping/hiding make-instance in a function (e.g. make-<class-name>), but I can't find either a reference or the reasoning.


PPS: Here's a rather old discussion (2004) about it in comp.lang.lisp (and another one from 2002). The main reasons people cite in favor of constructor functions are:

  1. Required arguments; achievable at runtime instead of at compile-time with :initform (error ...) in a slot that requires a provided initial value

  2. Generally, hide implementation details: class instance, structure instance, cons, something else

    2.1. Not wanting to export the actual class name

    2.2. Being able to return an instance of some other class, usually a subclass

  3. Convenient shorthand for a specific class

I striked always, because it seems proponents to constructor functions for CLOS objects don't necessarily want to hide the protocol that make-instance follows (allocate-instance, initialize-instanceshared-initialize) to implementers or extenders of the API or framework, although they might want to hide it to the consumers of the API or framework.


For something faster, you might want to access slots directly, but that doesn't use accessor methods, and hence doesn't support side-effects, e.g. :before and :after methods:

(defmacro gets (class var object) (let ((object-var (gensym))) `(let ((,object-var ,object)) (declare (optimize (speed 3) (safety 0) (debug 0)) (type ,class ,object-var)) (slot-value ,object-var ',var)))) 

This might be a direct slot access on some implementations.

Finally, you also have with-slots and with-accessors in the standard.

5 Comments

As stated, I was just toying around with macros etc, to see what can be done with them(I still have a huge problem really grasping how macros work etc) and not trying to create anything usefull. All in the process of learning you could say.
Using format, as I mentioned in my answer, is not a great idea. If the printer capitalization is changed, then you get unexpected symbols. For a while I used to keep print-case set to capitalize so, e.g., (print 'foo) would print Foo. But that means that (format nil "~a-~a" 'foo 'bar) produces "Foo-Bar", which makes for the symbol |Foo-Bar|, which is rather inconvenient to type with the default upcasing reader behavior. Using symbol-name and concatenate is much more to produce readable/writable symbols.
I've added an example to the end of my answer that shows why you shouldn't use format here.
@JoshuaTaylor, good point. Also, it's not format per se that is wrong, it's ~a (or princ) with a symbol. If instead of the symbol we'd provide its name (with either symbol-name or string), it would be OK.
Yes, you can avoid many of the issues with format if you give the actual strings as arguments. However, if you want any alphabetic text added it, it gets trickier. E.g., (format nil "COPY-~a" ...) or (format nil "copy-~a" ...), etc. "COPY-~a" is the most likely to work well, but (format nil "~a-~a" (string 'copy) ...) will at least get you the name of whatever copy is read as.
-4

Try playing with something like this:

(let ((a 'a) (dash '-) (b 'b)) `(,a,dash,b)) 

The other possibilities is to use intern, or more user friendly, alexandria's symbolicate.

1 Comment

This is pure disinformation, have you ever tried code like that? The reader doesn't concatenate ,a,dash,b just because you didn't include spaces, you must generate the symbol name.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.