问题描述
我在玩弄宏和Clos,在那里我创建了一个对象"宏来创建实例
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))
现在,我最终还是想对clos创建的访问器函数做类似的事情.示例:
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)))
然后创建实例
(setf p1 (object person :name "tom"))
现在显然要从对象中获取名称,我会称呼人名",但是与对象宏一样,我想创建一个"gets"宏来执行此操作.所以理想地:
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))
推荐答案
我认为我可能误解了最初的意图.起初,我以为您在问如何为类定义生成访问器名称,答案的哪一部分是地址.读完第二遍后,听起来实际上像是要生成一个新符号并用一些参数调用它.这也很容易,在此答案的第二部分中给出.第二部分和第三部分都取决于能否创建一个符号,该符号的名称由其他符号的名称构建而成,而这正是我们开始的目的.
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.
每个符号都有一个名称(字符串),您可以使用符号来获取-名称.您可以使用串联从一些旧字符串创建新字符串,然后使用 intern 获得具有新名称的符号.
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
重构访问者名称
(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)
但是,由于多种原因,这不是很可靠. (i)您不知道插槽是否具有格式为<class-name>-<slot-name>
的访问器. (ii)即使插槽确实具有形式为<class-name>-<slot-name>
的访问器,您也不知道它所在的包.在上面的代码中,我做出了合理的假设,即它与类名的包相同,但这根本不是必需的.例如,您可能拥有:
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)))
,那么这种方法根本行不通. (iii)这与继承关系不太好.如果您将person
子类化,例如使用north-american-person
,则仍然可以使用north-american-person
调用person-name
,但是不能使用任何东西调用north-american-person-name
. (iv)这似乎是在重新使用广告位值.您已经可以使用(slot-value object slot-name)
单独使用插槽的名称来访问插槽的值,并且我看不到任何原因,您的gets
宏不应仅扩展为该插槽.在那里,您不必担心访问器的特定名称(即使它有一个访问器),也不必担心类名称的包,而只需担心插槽的实际名称.
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.
您只需要提取符号的名称并生成具有所需名称的新符号即可.
如果您想自动生成具有defstruct样式名称的访问器,可以这样做:
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"
关于您的代码的其他注释
(defmacro object (class &rest args)
`(make-instance ',class ,@args))
Rainer指出不是很有用.在大多数情况下,它与
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))
除了可以使用(funcall #'object …)
和(apply #'object …)
使用该功能外,而不能使用宏.
except that you can (funcall #'object …)
and (apply #'object …)
with the function, but you can't with the macro.
您的gets宏实际上并没有比 slot-value ,其中包含一个对象和插槽名称.它不需要类的名称,即使该类没有读取器或访问器,它也可以工作.
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.
我一直在用连接符和符号名创建符号名.有时您会看到人们使用格式来构造名称,例如(format nil "~A-~A" 'person 'name)
,但这容易导致大写设置出现问题,并且可以更改.例如,在下面,我们定义一个函数foo-bar,并注意基于格式的方法失败了,但是基于串联的方法有效.
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"
这篇关于将两个变量组合成一个宏中的函数名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!