【发布时间】:2012-09-17 22:23:46
【问题描述】:
我有一个有多个插槽的课程。我还有一个构建器函数来创建该类的对象,以便将以下列表 '(:id "john" :name "John Doe" :age 42) 传递给该函数将构造一个具有这些插槽值的新对象。我将使用该函数生成多个对象,使用列表列表。
如何将 :id 之类的关键字转换为 SLOT-VALUE 可以使用的插槽名称?
谢谢。
【问题讨论】:
标签: common-lisp
我有一个有多个插槽的课程。我还有一个构建器函数来创建该类的对象,以便将以下列表 '(:id "john" :name "John Doe" :age 42) 传递给该函数将构造一个具有这些插槽值的新对象。我将使用该函数生成多个对象,使用列表列表。
如何将 :id 之类的关键字转换为 SLOT-VALUE 可以使用的插槽名称?
谢谢。
【问题讨论】:
标签: common-lisp
如果关键字是类的initargs,那么你可以通过APPLY调用MAKE-INSTANCE:
(defclass person ()
((id :initarg :id )
(name :initarg :name)
(age :initarg :age )))
CL-USER > (mapcar
(lambda (initargs)
(apply #'make-instance 'person initargs))
'((:id "john" :name "John Doe" :age 42)
(:id "mary" :name "Mary Doe" :age 42)))
(#<PERSON 402027AB7B> #<PERSON 402027AC33>)
【讨论】:
make-instance 的问题?我对您的问题的理解不同,因为问题的标题和正文说“如何将关键字转换为适合访问插槽的符号?”和“如何从 :id 之类的关键字转换为 SLOT-VALUE 可以使用的插槽名称?”。如果您的真正目标只是打电话给make-instance,而不是slot-value,那么Rainer Joswig 的解决方案是一种可行的方法。
(find-symbol...),但使用 KEYWORD 作为包,所以它不起作用。 Rainer 的回答只是给了我另一个观点,一个我没有考虑过的观点,这是正确的解决方案。谢谢。
find-symbol 和 symbol-name 函数将对您有所帮助。如果defclass 和slot-value 出现在同一个包中,您可以按如下方式使用这些函数:
(defclass person ()
((id :initarg :id)
(name :initarg :name)
(age :initarg :age)))
(slot-value (make-instance 'person :id "john" :name "John Doe" :age 42)
(find-symbol (symbol-name :id)))
如果defclass 和slot-value 出现在两个不同的包中,您需要给find-symbol 提供defclass 出现的包的名称:
(in-package #:common-lisp-user)
(defpackage #:foo
(:use #:common-lisp)
(:export #:person))
(defpackage #:bar
(:use #:common-lisp #:foo))
(in-package #:foo)
(defclass person ()
((id :initarg :id)
(name :initarg :name)
(age :initarg :age)))
(in-package #:bar)
(slot-value (make-instance 'person :id "john" :name "John Doe" :age 42)
(find-symbol (symbol-name :id) 'foo))
(find-symbol name &optional (package (sane-package)))功能:返回PACKAGE中名为STRING的符号。如果找到这样的符号,则第二个值是 :INTERNAL、:EXTERNAL 或 :INHERITED 以指示如何访问该符号。如果没有找到符号,那么这两个值都是 NIL。
(symbol-name symbol)功能:以字符串形式返回 SYMBOL 的名称。
【讨论】:
我知道这已经很老了,但我认为这里最重要的一点是:
不要那样使用slot-value!
要获取访问器,请使用:accessor 或:reader 插槽选项,而要将值传递给构造函数,请使用:initarg:
(defclass foo ()
((bar :accessor foo-bar :initarg :bar)))
这意味着:创建一个 getter 方法和一个名为 foo-bar 的 setf 扩展器,并使用一个名为 :bar 到 make-instance 的关键字参数来初始化此插槽的值。
现在你可以像这样实例化这样一个对象:
(make-instance 'foo :bar "quux")
或者,如果你得到一个 initargs 的属性列表(正如 Rainer 已经展示的那样):
(let ((initargs (list :bar "quux"))) ; getting this from somewhere
(apply #'make-instance 'foo initargs))
然后你可以得到这样的值:
(foo-bar some-foo)
并像往常一样使用setf 设置它:
(setf (foo-bar some-foo) "wobble")
如果您使用:reader 而不是:accessor,则不允许设置。这对于传达不变性的意图通常很有用。
Slot-value 确实适用于对象生命周期中的特殊情况,例如在使用initialize-instance 的方法时。这是一个高级主题。
【讨论】:
我对这种愚蠢的 CL 的解决方案是:
(defun locate-symbol
(inst kw)
(let* ((slot-name (symbol-name kw))
(slot-def (find slot-name
(clos:compute-slots (class-of inst))
:test #'(lambda (name sd)
(string= name
(symbol-name (clos:slot-definition-name sd)))))))
(if slot-def
(clos:slot-definition-name slot-def)
(error "Can't find a slot definition named ~s." slot-name))))
(defun gets
(self slot-name)
"Get a value of a slot by its name (keyword)"
(slot-value self (locate-symbol self slot-name)))
(defun sets!
(self slot-name value)
"Set a value of a slot by its name (keyword)"
(setf (slot-value self (locate-symbol self slot-name))
value))
所以现在你可以这样做了:
(defvar obj (make-instance '<some-class>))
(sets! obj :some-slot "some value")
(format t "-> ~a~%" (gets obj :some-slot))
【讨论】: