如何使用任意数量的args在lisp中定义结构?

时间:2013-07-09 17:18:52

标签: lisp structure common-lisp

我正在尝试定义一个具有我想知道的属性的结构,以及基本结构不需要的任意数量的其他属性。

(defstruct (node (:type list)) label [other args here])

我知道你可以做的一个功能:

(defun foo (arg1 &rest args) ...)

&rest是否有某种defstruct等价物?

我只是在学习口齿不清,所以我觉得我错过了一些东西。如果没有&rest等价物,那么我对此怎么办?提前谢谢!

3 个答案:

答案 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只是carfirst的别名。

(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知之甚少,但我对另一种语言中的这种操作感到非常悲痛,这种情况很普遍,而且通常不鼓励。