我现在已经了解了Lisp中的数组和aref
。到目前为止,它很容易掌握,并且像一个烟囱一样工作:
(defparameter *foo* (make-array 5))
(aref *foo* 0) ; => nil
(setf (aref *foo* 0) 23)
(aref *foo* 0) ; => 23
使我困惑的是当您将
aref
和aref
结合使用时发生的setf
“魔术”。似乎aref
知道其调用上下文,然后将决定是返回setf
可以使用的值还是位置。无论如何,就目前而言,我只是认为这是理所当然的,并且不考虑它在内部的工作方式过多。
但是现在我想创建一个将
*foo*
数组的元素设置为预定义值的函数,但是我不想对*foo*
数组进行硬编码,而是要移交一个位置:(defun set-23 (place)
…)
因此,基本上该功能将位置设置为23,无论位置在哪里。我最初的天真做法是
(defun set-23 (place)
(setf place 23))
并使用以下命令调用它:
(set-23 (aref *foo* 0))
这不会导致错误,但也完全不会更改
*foo*
。我的猜测是对aref
的调用解析为nil
(因为该数组当前为空),所以这意味着(setf nil 23)
已运行,但是当我在REPL中手动尝试此操作时,出现错误告诉我:
(这绝对有道理!)
所以,最后我有两个问题:
set-23
函数正常工作? 我也有想法为此使用thunk来推迟
aref
的执行,就像这样:(defun set-23 (fn)
(setf (funcall fn) 23))
但是当我尝试定义此功能时,这已经遇到了错误,因为Lisp现在告诉我:
同样,我想知道为什么会这样。为什么将
setf
与funcall
结合使用显然对命名函数有效,但不适用于lambda,例如?PS:在“Lisp土地”(我正在阅读以了解Lisp的土地)中说:
好吧,我想这就是这里的原因(或者至少是原因之一),为什么所有这些都不如我所期望的那样起作用,但是尽管如此,我还是想知道更多信息:-)
最佳答案
一个地方并没有什么物理意义,它只是我们可以获取/设置值的任何事物的概念。因此,一般来说,一个地方不能退回或通过。 Lisp开发人员希望有一种方法,可以通过仅知道 setter/getter 是什么就可以轻松猜出二传手。因此,我们用周围的setf
形式编写 setter/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
请注意,上述设置函数可能在内部存在。但是您不需要通过
setf
来了解它们。结果:我们最终得到了许多不同的setter函数名称。
SETF
机制用一种通用语法替换了所有它们。你知道getter电话吗?然后,您也知道二传手。 getter调用周围只是setf
加上新值。另一个例子
world-time ; may return the world time
(setf world-time (get-current-time)) ; sets the world time
等等...
还要注意,只有宏才能处理设置位置:
setf
,push
,pushnew
,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
是变量,并且绑定(bind)到audi-vehicle
类的对象。要设置速度,我们使用setf
调用writer方法。Lisp是从哪里认识作家的?例如,一个类声明生成一个:
(defclass audi-vehicle ()
((speed :accessor vehicle-speed)))
:accessor vehicle-speed
声明使读取和设置函数都被生成。setf
宏查看已注册的setter的宏扩展时间。就这样。所有setf操作看起来都相似,但是下面的Lisp知道如何设置。以下是
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))
设置变量:
CL-USER 87 > (pprint (macroexpand-1 '(setf a 'foo)))
(LET* ((#:|Store-Var-10336877| 'FOO))
(SETQ A #:|Store-Var-10336877|))
设置CLOS插槽:
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)
如您所见,它在扩展中使用了很多内部代码。用户只需编写一个
SETF
表单,Lisp便会找出实际上可以执行此操作的代码。由于您可以编写自己的 setter ,因此只有您的想象力才能限制您可能希望使用以下通用语法编写的内容:
关于lisp - 试图了解setf + aref "magic",我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24094927/