Common Lisp对象的setter函数样式

时间:2015-02-22 04:30:13

标签: coding-style common-lisp

我正在尝试编写一个函数,该函数接受一个名为nodes的用户定义对象列表,以生成它们之间的连接。每个node对象都有一个插槽用于其唯一编号(&#39; num&#39;)和一个插槽,用于作为节点之间边缘的数字列表(&#39;边缘&#39;)。 +max-edges+是一个整数,用于定义尝试边缘配对的次数,+max-rooms+是节点列表中传递给函数的节点数(并且始终<50)。

以下是试图解决此问题的函数的两个版本:

(defun connect-nodes (node-list)
  "Given a NODE-LIST, repeats for +MAX-EDGES+ amount of times
to alter NODE-LIST in-place to connect randomly generated edges to nodes."
  (loop repeat +max-edges+
     do (let ((begin-node (random +max-rooms+))
              (end-node (random +max-rooms+)))
          (when (not (= begin-node end-node))
            (setf (slot-value (nth begin-node node-list) 'edges)
                  (cons end-node
                        (slot-value (nth begin-node node-list) 'edges)))
            (setf (slot-value (nth end-node node-list) 'edges)
                  (cons begin-node
                        (slot-value (nth end-node node-list) 'edges))))))))

(defun connect-nodes% (node-list)
  "Given a NODE-LIST, repeats for +MAX-EDGES+ amount of times
to alter NODE-LIST in-place to connect randomly generated edges to nodes."
  (loop repeat +max-edges+
     do (let ((begin-node (random +max-rooms+))
              (end-node (random +max-rooms+)))
          (when (not (= begin-node end-node))
            (let ((begin-node-lst (slot-value (nth begin-node node-list) 'edges))
                  (end-node-lst (slot-value (nth end-node node-list) 'edges)))
              (setf begin-node-lst (cons end-node begin-node-lst))
              (setf end-node-lst (cons begin-node end-node-lst)))))))

(connect-nodes)按预期工作,但最后两行似乎风格较长,并且查找对象的插槽值为setf两次,我认为这可能是性能问题。

(connect-nodes%)尝试通过在词法范围内绑定位置来解决双重查找,但实际上并未实际更改node-list参数。没有进行任何更改,因为let绑定(begin-node-lstend-node-lst)中的每个位置仅在词汇上绑定,并且在setf s之后超出范围。

所以我要求澄清几点:

  • 我是否理解为什么第二个函数无法改变参数列表的正确性?
  • 第一个功能在风格上是否正确?是否有更好的方法来编写此函数,该函数不会为setf查找两次槽值,或者这对于小长度列表是否可接受?

如果这会影响你的答案,我正在运行史莱姆+ emacs + sbcl。

修改 根据我的问题答案的建议,这就是我最终选择connect-nodes函数的列表版本的内容。我正在开发一个适用于矢量的版本,因此这个版本的connect-nodes是泛型函数的一个方法:

(defmethod connect-nodes ((node-list list))
  "Given a NODE-LIST, repeats for +MAX-EDGES+ amount of times
to alter NODE-LIST in-place to connect randomly generated edges to nodes."
  (loop repeat +max-edges+
     do (let ((begin-node (random +max-rooms+))
              (end-node (random +max-rooms+)))
          (when (not (= begin-node end-node))
            (push end-node (edges (nth begin-node node-list)))
            (push begin-node (edges (nth end-node node-list)))))))

2 个答案:

答案 0 :(得分:4)

您对第二个功能的理解是正确的。

您可能希望将实际节点存储在edges插槽中而不是节点编号中。然后,不是将局部变量绑定到要连接的两个节点内的节点列表,而是将它们绑定到节点本身,这看起来也比内部nth的重复调用更好。 setf表格。然后,当您访问edges时,您也可以直接使用节点进行操作,而不必执行额外的查找。

为了改善第一个功能的风格,我建议两件事:

使用push代替(setf ... (cons thing ...))

slot-value是一个访问者,因此,它可以用作一个地方。 setf是改变场所价值的一种方法,但Common Lisp定义了场所的其他操作。您在此处使用的模式在宏push中实现。通过使用它,您可以显着简化表达式:

(push end-node (slot-value (nth begin-node node-list) 'edges))

定义边的访问器而不是使用slot-value

slot-value应该很少使用,并且作为一种低级机制,因为它比使用命名访问器更冗长,更灵活。 slot-value还将访问的重要部分(插槽的名称)放在表达式的末尾,这通常会使代码难以阅读。在您的情况下,我会在类定义中命名访问者edges

(edges :initform nil :accessor edges)

这会让你的第一个版本更具可读性:

(push end-node (edges (nth begin-node node-list)))

答案 1 :(得分:3)

而不是:

(setf (slot-value (nth begin-node node-list) 'edges)
      (cons end-node (slot-value (nth begin-node node-list) 'edges)))

你可以写:

(push end-node (slot-value (nth begin-node node-list) 'edges))

为什么以下内容无法按预期工作?

(let ((begin-node-lst (slot-value (nth begin-node node-list) 'edges))
      (end-node-lst (slot-value (nth end-node node-list) 'edges)))
  (setf begin-node-lst (cons end-node begin-node-lst))
  (setf end-node-lst (cons begin-node end-node-lst)))

您写道:尝试通过绑定位置来解决双重查找

这不起作用。您可以绑定位置。您只能绑定值。 LET将表单的值绑定到变量。

在Common Lisp中有一个 place 的想法。许多副作用宏适用于地点:SETFPUSH是示例。地方只是访问代码的来源,而不是真正的第一类对象

地方的例子:

  • foo作为变量
  • (aref foo 10)作为数组访问
  • (slot-value object 'foo)作为广告位访问
  • (slot-value (find-object *somewhere* 'foo) 'bar)作为广告位访问...

SETF这样的宏根据访问表单的来源,在宏展开时找到了生成设置表单的形式。它无法查看绑定等绑定形式的内容。

在这种情况下,通常会从数据结构中检索对象(通常是CLOS对象或结构),保留对对象的引用,然后使用SLOT-VALUEWITH-SLOTS更改槽值。或者使用访问者。

(setf (slot-value person 'name)  "Eva Lu Ator")
(setf (slot-value person 'group) :development)

将是

(with-slots (name group) person
  (setf name  "Eva Lu Ator"
        group :development))

一般建议

在你的函数中还要注意node是什么的混淆。它是node类型的对象还是数字?如果是数字,我会将变量命名为node-number

避免使用NTH和列表。如果您需要随机访问,请使用向量。

直接使用节点对象(而不是那些数字)或使用符号:node-123并将节点符号链接到某个注册表中的节点对象。您可能只想在某些情况下使用数字......

我会写这样的代码:

(defun connect-nodes (node-vector)
  "Given a NODE-VECTOR, repeats for +MAX-EDGES+ amount of times to connect
nodes via randomly generated edges."
  (loop repeat +max-edges+
        for begin-node-number = (random +max-rooms+) and
            end-node-number   = (random +max-rooms+)
        when (/= begin-node-number end-node-number) do
        (let ((begin-node (aref node-vector begin-node-number))
              (end-node   (aref node-vector begin-node-number)))
          (push end-node   (slot-value begin-node 'edges))
          (push begin-node (slot-value end-node   'edges))))
  node-vector)