6

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

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

我知道在一个函数中你可以做到:

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

是否有某种&rest等价物defstruct

我只是在学习 lisp,所以我觉得我错过了一些东西。如果没有&rest等价物,关于我如何解决这个问题的任何想法?提前致谢!

4

3 回答 3

8

在 Common Lisp 中,结构被认为是严格的低级记录。他们没有花哨的动态功能。

你可以用结构做的是定义一个新的结构类型,它继承自另一个结构类型。有单继承可用。

为了处理动态可扩展性,一种典型的方法是在结构中添加一个属性列表槽。见约书亚的回答。

然后是 Common Lisp 对象系统,它提供多重继承,您可以在运行时更改类。因此,您可以向一个类添加一个插槽,并且该类的实例会自行更新。您还可以更改对象的类,并且可能会添加或删除插槽。尽管如此,通常一个类的所有实例都将具有相同的插槽集。同样,人们看到可以添加带有属性列表的插槽并将其用于可扩展性。

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
于 2013-07-09T19:14:23.140 回答
6

目前尚不清楚您在寻找什么。结构的默认情况是具有固定数量的槽的记录类型,每个槽都有一个名称,并且可以通过defstruct宏生成的函数访问。例如,一旦你完成了

(defstruct node 
  label)

您可以访问 anode的标签node-label并获得快速的查找时间(因为它通常只是内存块的索引)。现在,正如您所做的那样,您可以选择使用列表作为结构的实现,在这种情况下,node-label它只是caror的别名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))

如果您正在寻找任意基于列表的键值对,您可能需要一个属性 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)
于 2013-07-09T18:15:59.467 回答
2

我认为这值得单独回复而不是评论,所以这里是:

有时候,当你认为你需要一个结构或一个对象,但是你有一些这些实体没有满足的特殊要求时,也许这是因为你真正需要的是一些不同的数据结构?当满足某些条件时,对象或结构是好的,其中一个条件是插槽是静态已知的 - 这允许编译器更好地推理代码,这对优化和错误报告都有好处。

另一方面,还有数据结构。有些随语言标准库提供,有些则在其之上添加。这是一个提供其中许多的库:http ://cliki.net/cl-containers但对于特殊情况还有更多。

现在,我将争辩说,使用诸如列表、数组、某种树等结构比尝试扩展对象或结构以允许动态添加槽更好。这是因为通常我们预计访问插槽的时间可以忽略不计。也就是说,我们期望它是 O(1)。无论对象有多少槽,这通常会发生。现在,当您使用下面的列表时,您将使其成为 O(n),同时保持相同的语义!当然,您可以使用哈希表将其设为 O(1)(尽管这通常仍会比插槽访问慢),但随后您将遇到一些其他意外行为,例如nil当插槽不存在时返回而不是常规错误等。

我不认为以这种方式扩展对象是 CL 中的常见做法,这可能就是为什么其他响应不会阻止您这样做的原因。与其他受访者相比,我对 CL 的了解更少,但我对另一种语言的这种操作感到非常悲痛,这种操作很常见,而且通常不鼓励。

于 2013-07-10T06:15:39.330 回答