试图理解setf + aref"魔法"

Gol*_*den 5 lisp common-lisp

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

(defparameter *foo* (make-array 5))
(aref *foo* 0) ; => nil
(setf (aref *foo* 0) 23)
(aref *foo* 0) ; => 23
Run Code Online (Sandbox Code Playgroud)

令我百思不解的是aref"神奇"当你把这种情况发生arefsetf.似乎aref知道它的调用上下文,然后决定是否返回一个值或一个可以使用的地方setf.

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

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

(defun set-23 (place)
  …)
Run Code Online (Sandbox Code Playgroud)

所以基本上这个函数集放置到23,任何地方.我最初的天真的方法是

(defun set-23 (place)
  (setf place 23))
Run Code Online (Sandbox Code Playgroud)

并使用以下方式调用:

(set-23 (aref *foo* 0))
Run Code Online (Sandbox Code Playgroud)

这不会导致错误,但也不会发生任何变化*foo*.我的猜测是aref解析的调用nil(因为数组当前是空的),所以这意味着

(setf nil 23)
Run Code Online (Sandbox Code Playgroud)

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

NIL是常量,不能用作变量

(这绝对有道理!)

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

  1. 我的样本中发生了什么,这不会导致错误,为什么它不做任何事情?
  2. 我怎么能解决这个问题才能让我的set-23功能发挥作用?

我也有想法使用thunk来推迟执行aref,就像:

(defun set-23 (fn)
  (setf (funcall fn) 23))
Run Code Online (Sandbox Code Playgroud)

但是当我尝试定义这个函数时,这已经遇到错误,因为Lisp现在告诉我:

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

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

PS:在"Lisp的土地"(我目前正在阅读以了解Lisp)它说:

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

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

Rai*_*wig 9

一个地方不是物质的,它只是我们可以获得/设定价值的任何概念.所以一般地方不能归还或通过.Lisp开发人员希望通过知道getter是什么来轻松猜测一个setter.所以我们用一个周围的setf形式编写getter,并且Lisp弄清楚如何设置一些东西:

      (slot-value vehicle 'speed)        ; gets the speed
(setf (slot-value vehicle 'speed) 100)   ; sets the speed
Run Code Online (Sandbox Code Playgroud)

没有SETF我们需要一个名字的setter函数:

(set-slot-value vehicle 'speed 100)      ; sets the speed
Run Code Online (Sandbox Code Playgroud)

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

(set-aref 3d-board 100 100 100 'foo)    ; sets the board at 100/100/100
Run Code Online (Sandbox Code Playgroud)

请注意,上述setter函数可能存在于内部.但你不需要了解它们setf.

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

另一个例子

      world-time                         ; may return the world time
(setf world-time (get-current-time))     ; sets the world time
Run Code Online (Sandbox Code Playgroud)

等等...

另请注意,只有宏处理设置的地方:setf,push,pushnew,remf,...仅在那些你可以设置一个场所.

(defun set-23 (place)
  (setf place 23))
Run Code Online (Sandbox Code Playgroud)

上面可以写,但place只是一个变量名.你无法通过一个地方.让我们重命名它,这不会改变一件事,但会减少混乱:

(defun set-23 (foo)
  (setf foo 23))
Run Code Online (Sandbox Code Playgroud)

foo是一个局部变量.局部变量是一个地方.我们可以设置的东西.所以我们可以setf用来设置变量的本地值.我们没有设置传入的东西,我们自己设置变量.

(defmethod set-24 ((vehicle audi-vehicle))
  (setf (vehicle-speed vehicle) 100))
Run Code Online (Sandbox Code Playgroud)

在上面的方法中,vehicle是一个变量,它绑定到类的对象audi-vehicle.要设置它的速度,我们setf用来调用writer方法.

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

(defclass audi-vehicle ()
   ((speed :accessor vehicle-speed)))
Run Code Online (Sandbox Code Playgroud)

:accessor vehicle-speed声明导致生成读取和设置功能两者.

setf宏观着眼于宏扩展时间注册的制定者.就这样.所有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))
Run Code Online (Sandbox Code Playgroud)

设置变量:

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

(LET* ((#:|Store-Var-10336877| 'FOO))
  (SETQ A #:|Store-Var-10336877|))
Run Code Online (Sandbox Code Playgroud)

设置CLOS插槽:

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

(CLOS::SET-SLOT-VALUE O1 'BAR 'FOO)
Run Code Online (Sandbox Code Playgroud)

设置列表的第一个元素:

CL-USER 89 > (pprint (macroexpand-1 '(setf (car some-list) 'foo)))

(SYSTEM::%RPLACA SOME-LIST 'FOO)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它在扩展中使用了大量内部代码.用户只需编写一个SETF表单,Lisp会确定代码实际上会做什么.

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

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


Mar*_*ark 6

在你的例子中:

(defun set-23 (place)
  (setf place 23))
Run Code Online (Sandbox Code Playgroud)

你不能这样做,因为你必须setf上下文中使用.这将有效:

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

CL-USER> (set-23 (aref *foo* 0))
23
CL-USER> *foo*
#(23 NIL NIL NIL NIL)
Run Code Online (Sandbox Code Playgroud)

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

setf 是一个宏,并以你想要的方式使用它,你还必须使用宏.

你没有得到错误的原因是你实际上成功修改了place绑定到所选数组元素副本的词法变量.