我正在尝试定义一个具有我想知道的属性的结构,以及基本结构不需要的任意数量的其他属性。
(defstruct (node (:type list)) label [other args here])
我知道你可以做的一个功能:
(defun foo (arg1 &rest args) ...)
&rest
是否有某种defstruct
等价物?
我只是在学习口齿不清,所以我觉得我错过了一些东西。如果没有&rest
等价物,那么我对此怎么办?提前谢谢!
答案 0 :(得分:7)
在Common Lisp中,结构被认为是刚性和低级记录。它们没有花哨的动态功能。
你可以用结构做的是定义一个从另一个继承的新结构类型。有单一的继承可用。
要处理动态可扩展性,一种典型的方法是向结构添加属性列表槽。见约书亚的答案。
然后是Common Lisp Object System,它提供多重继承,您可以在运行时更改类。因此,您可以向类添加一个插槽,该类的实例会自行更新。您还可以更改对象的类,可以添加或删除插槽。但是,通常所有类的实例都将具有相同的插槽集。再次,可以看到可以添加具有属性列表的插槽并将其用于可扩展性。
Common Lisp还有其他对象系统,可以轻松地在每个实例的基础上添加插槽。但是,通常使用它们通常太多了,因为它们的功能更强大。
使用CLOS和元对象协议,可以尝试隐藏它。我在这里使用LispWorks:
我们为我们的属性定义了一个mixin类:
(defclass property-mixin ()
((plist :initform nil))
#+lispworks
(:optimize-slot-access nil))
设置和阅读属性:
(defmethod set-property ((object property-mixin) key value)
(setf (getf (slot-value object 'plist) key) value))
(defmethod get-property ((object property-mixin) key)
(getf (slot-value object 'plist) key))
现在我们编写方法使SLOT-VALUE
接受我们的属性名称:
(defmethod (setf clos:slot-value-using-class)
(value (class standard-class) (object property-mixin) slot-name)
(declare (ignorable class))
(if (slot-exists-p object slot-name)
(call-next-method)
(progn
(set-property object slot-name value)
value)))
(defmethod clos:slot-value-using-class ((class standard-class)
(object property-mixin)
slot-name)
(declare (ignorable class))
(if (slot-exists-p object slot-name)
(call-next-method)
(get-property object slot-name)))
实施例。我们定义了一个有两个插槽的汽车类:
(defclass automobile (property-mixin)
((company :initarg :company)
(motor :initarg :motor))
#+lispworks
(:optimize-slot-access nil))
现在是一个实例:
CL-USER 45 > (setf a6 (make-instance 'automobile :company :audi :motor :v6))
#<AUTOMOBILE 402005B47B>
我们可以获得正常的插槽值:
CL-USER 46 > (slot-value c1 'motor)
:V6
让我们写一个不存在的插槽,但会被添加到我们的属性列表中:
CL-USER 47 > (setf (slot-value a6 'seats) 4)
4
我们可以获得价值:
CL-USER 48 > (slot-value c1 'seats)
4
答案 1 :(得分:6)
目前尚不清楚您正在寻找什么。结构的默认情况是具有固定数量的槽的记录类型,每个槽都有一个名称,并且可以通过defstruct
宏生成的函数访问。例如,一旦你完成了
(defstruct node
label)
您可以使用node
访问node-label
标签,并获得快速查找时间(因为它通常只是内存块的索引)。现在,正如您所做的那样,您可以选择使用列表作为结构的实现,在这种情况下,node-label
只是car
或first
的别名。
(defstruct (node (:type list))
label)
CL-USER> (make-node :label 'some-label)
(SOME-LABEL)
CL-USER> (node-label (make-node :label 'some-label))
SOME-LABEL
CL-USER> (first (make-node :label 'some-label))
SOME-LABEL
CL-USER> (car (make-node :label 'some-label))
如果您正在寻找任意基于列表的键值对,您可能需要property list,Common Lisp包含一些便利功能。
如果您想拥有一个也包含属性列表的结构,您可以添加一个特殊的构造函数来填充该列表。例如,
(defstruct (node (:type list)
(:constructor make-node (label &rest plist)))
label
plist)
CL-USER> (make-node 'some-label :one 1 :two 2)
(SOME-LABEL (:ONE 1 :TWO 2))
CL-USER> (node-plist (make-node 'some-label :one 1 :two 2))
(:ONE 1 :TWO 2)
答案 2 :(得分:1)
我认为这值得单独回复而不是评论,所以这里是:
有些时候,当你认为你需要一个结构或一个对象,但是你有一些这些实体不能满足的特殊要求时,也许这是因为你真正需要的是一些不同的数据结构?当满足某些条件时,对象或结构是好的,一个这样的条件是槽是静态已知的 - 这允许编译器更好地推理代码,这有利于优化和错误报告。
另一方面,有数据结构。一些提供了语言标准库,其他一些在其上添加。这是一个提供其中许多库的库:http://cliki.net/cl-containers但是对于特殊情况还有更多。
现在,我将论证使用诸如列表,数组,某种树等结构比尝试扩展对象或结构以允许动态添加插槽更好。这是因为通常我们希望访问插槽的时间可以忽略不计。那是我们期望它是O(1)。无论对象具有多少个槽,通常都会发生这种情况。现在,当你使用下面的列表时,你正在使它成为O(n),而你保持相同的语义!当然,您可以使用散列表将其设为O(1)(尽管这通常比插槽访问速度慢),但是您会遇到其他一些意外行为,例如nil
时返回插槽不存在而不是常规错误等。
我不认为以这种方式扩展对象是CL中的常见做法,这可能是为什么其他响应不会阻止您这样做的原因。我对其他受访者的CL知之甚少,但我对另一种语言中的这种操作感到非常悲痛,这种情况很普遍,而且通常不鼓励。