试图理解setf + aref“魔法”

时间:2014-06-07 07:34:34

标签: lisp common-lisp

我现在已经了解了Lisp中的数组和aref。到目前为止,它很容易掌握,它就像一个魅力:

(defparameter *foo* (make-array 5))
(aref *foo* 0) ; => nil
(setf (aref *foo* 0) 23)
(aref *foo* 0) ; => 23

让我感到困惑的是aref"魔法"组合arefsetf时会发生这种情况。似乎aref知道其调用上下文,然后决定是否返回setf可以使用的值或位置。

无论如何,目前我只是认为这是理所当然的,并且不要过多考虑内部工作方式。

但是现在我想创建一个将*foo*数组的元素设置为预定义值的函数,但我不想对*foo*数组进行硬编码,而是我想要交出一个地方:

(defun set-23 (place)
  …)

所以基本上这个函数将 place 设置为23,无论在哪里。我最初的天真方法是

(defun set-23 (place)
  (setf place 23))

并使用:

调用它
(set-23 (aref *foo* 0))

这不会导致错误,但也根本不会改变*foo*。我的猜测是对aref的调用解析为nil(因为数组当前为空),所以这意味着

(setf nil 23)

运行,但是当我在REPL中手动尝试时,我收到错误告诉我:

  

NIL是常量,不能用作变量

(这绝对有道理!)

所以,最后我有两个问题:

  1. 我的样本中发生了什么,这不会导致错误,为什么它没有做任何事情?
  2. 我如何解决此问题以使我的set-23功能正常工作?
  3. 我还有一个想法是使用thunk来推迟执行aref,就像:

    (defun set-23 (fn)
      (setf (funcall fn) 23))
    

    但是当我尝试定义这个函数时,这已经遇到了错误,正如Lisp现在告诉我的那样:

      

    (SETF FUNCALL)仅针对形式为#'符号的函数定义。

    再次,我想知道为什么会这样。为什么将setffuncall结合使用显然适用于命名函数,但不适用于lambdas,例如?

    PS:在Lisp" Land of Lisp" (我目前正在阅读以了解Lisp)它说:

      

    实际上,setf中的第一个参数是Common Lisp的一个特殊子语言,称为通用引用。不是每个Lisp命令都允许在通用引用中,但你仍然可以放入一些非常复杂的东西:[...]

    嗯,我想这就是原因(或者至少有一个原因),为什么所有这些都不能像我期望的那样起作用,但是我很想知道更多: - )

2 个答案:

答案 0 :(得分:8)

地点不是物理,它只是我们可以获取/设置值的任何概念。因此,通常无法返回或传递地点。 Lisp开发人员希望通过知道getter是什么来轻松猜测一个setter。所以我们用一个周围的setf形式编写getter,Lisp计算出如何设置:

      (slot-value vehicle 'speed)        ; gets the speed
(setf (slot-value vehicle 'speed) 100)   ; sets the speed

如果没有SETF,我们需要一个名字为:

的setter函数
(set-slot-value vehicle 'speed 100)      ; sets the speed

要设置数组,我们需要另一个函数名:

(set-aref 3d-board 100 100 100 'foo)    ; sets the board at 100/100/100

请注意,上面的setter函数可能在内部存在。但您不需要通过setf了解它们。

结果:我们最终得到了许多不同的setter函数名。 SETF机制用一种通用语法替换所有这些机制。你知道getter的电话吗?然后你也知道了二传手。它只是{* 1}}围绕getter调用加上新值。

另一个例子

setf

等等......

另请注意,只有宏处理设置位置: world-time ; may return the world time (setf world-time (get-current-time)) ; sets the world time setfpushpushnew,...仅限于您可以设置地点。< / p>

remf

上面可以写,但(defun set-23 (place) (setf place 23)) 只是一个变量名。你不能通过一个地方。让我们重命名它,这不会改变一件事,但会减少混淆:

place

这里(defun set-23 (foo) (setf foo 23)) 是一个局部变量。局部变量是的地方。我们可以设置的东西。因此我们可以使用foo来设置变量的本地值。我们没有设置传入的内容,我们自己设置变量。

setf

在上面的方法中,(defmethod set-24 ((vehicle audi-vehicle)) (setf (vehicle-speed vehicle) 100)) 是一个变量,它绑定到类vehicle的对象。要设置它的速度,我们使用audi-vehicle来调用writer方法。

Lisp从哪里知道作者?例如,类声明生成一个:

setf

(defclass audi-vehicle () ((speed :accessor vehicle-speed))) 声明会导致生成读取和设置功能。

:accessor vehicle-speed宏查看已注册的setter的宏扩展时间。这就是全部。所有setf操作看起来都很相似,但是下面的Lisp知道如何设置。

以下是setf使用的一些示例,已展开:

在索引处设置数组项:

SETF

设置变量:

CL-USER 86 > (pprint (macroexpand-1 '(setf (aref a1 10) 'foo)))

(LET* ((#:G10336875 A1) (#:G10336876 10) (#:|Store-Var-10336874| 'FOO))
  (SETF::\"COMMON-LISP\"\ \"AREF\" #:|Store-Var-10336874|
                                   #:G10336875
                                   #:G10336876))

设置CLOS插槽:

CL-USER 87 > (pprint (macroexpand-1 '(setf a 'foo)))

(LET* ((#:|Store-Var-10336877| 'FOO))
  (SETQ A #:|Store-Var-10336877|))

设置列表的第一个元素:

CL-USER 88 > (pprint (macroexpand-1 '(setf (slot-value o1 'bar) 'foo)))

(CLOS::SET-SLOT-VALUE O1 'BAR 'FOO)

正如您所看到的,它在扩展中使用了大量内部代码。用户只需编写一个CL-USER 89 > (pprint (macroexpand-1 '(setf (car some-list) 'foo))) (SYSTEM::%RPLACA SOME-LIST 'FOO) 表单,Lisp会找出实际执行该操作的代码。

既然你可以编写自己的setter,那么只有你的想象力限制了你可能希望用这种通用语法提出的东西:

  • 通过某种网络协议在另一台计算机上设置值
  • 在您刚刚发明的自定义数据结构中设置一些值
  • 在数据库中设置值

答案 1 :(得分:6)

在你的例子中:

(defun set-23 (place)
  (setf place 23))

你不能这样做,因为你必须在 context 中使用setf。 这将有效:

(defmacro set-23 (place)
  `(setf ,place 23))

CL-USER> (set-23 (aref *foo* 0))
23
CL-USER> *foo*
#(23 NIL NIL NIL NIL)

诀窍是,setf'知道'如何查看其参数来自的真实位置,仅适用于有限数量的函数。这些函数称为 setfable

setf是一个宏,要按照您想要的方式使用它,您还必须使用宏。

您没有收到错误的原因是您实际上成功修改了绑定到所选数组元素的 copy 的词法变量place