首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >试着理解setf + aref“魔法”

试着理解setf + aref“魔法”
EN

Stack Overflow用户
提问于 2014-06-07 07:34:33
回答 2查看 1.2K关注 0票数 5

我现在已经了解了Lisp中的数组和aref。到目前为止,它很容易理解,而且它的工作原理就像一种特性:

代码语言:javascript
复制
(defparameter *foo* (make-array 5))
(aref *foo* 0) ; => nil
(setf (aref *foo* 0) 23)
(aref *foo* 0) ; => 23

让我困惑的是当你把arefsetf结合在一起的时候发生的“魔法”。似乎aref知道它的调用上下文,然后决定是返回一个值,还是返回一个setf可以使用的位置。

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

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

代码语言:javascript
复制
(defun set-23 (place)
  …)

基本上,这个函数将place设为23,不管place是什么。我最初的天真做法是

代码语言:javascript
复制
(defun set-23 (place)
  (setf place 23))

并使用以下方法进行调用:

代码语言:javascript
复制
(set-23 (aref *foo* 0))

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

代码语言:javascript
复制
(setf nil 23)

正在运行,但当我在REPL中手动尝试此操作时,会收到一个错误,告诉我:

0是一个常数,不能用作变量。

(这绝对是有道理的!)

最后,我有两个问题:

  1. 在我的样本中发生了什么,这不会导致错误,为什么它什么都不做呢?
  2. 如何解决这个问题以使我的set-23函数正常工作?

我还想为此使用一个thunk来推迟aref的执行,如下所示:

代码语言:javascript
复制
(defun set-23 (fn)
  (setf (funcall fn) 23))

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

(SETF FUNCALL)只为#‘符号的函数定义。

再说一次,我不知道这是为什么。为什么结合使用setffuncall显然可以用于指定的函数,而不适用于lambdas,例如?

PS:在“Lisp之地”(我目前正在阅读它来了解Lisp)中写着:

实际上,setf中的第一个参数是Common的一种特殊的子语言,称为广义引用。并不是每个Lisp命令都允许在通用引用中使用,但是仍然可以放入一些非常复杂的内容:…

好吧,我想这就是这里的原因(或者至少是其中一个原因),为什么所有这些都不像我期望的那样起作用,但我还是很想了解更多:-)

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-06-07 08:10:42

一个地方不是物理的,它只是一个概念,任何我们可以得到/设置一个值的东西。所以一个地方一般是不能归还或通过的。Lisp开发人员想要一种简单的方法,从知道getter是什么开始就可以很容易地猜出一个setter。因此,我们用一个周围的setf表单编写了getter,Lisp指出了如何设置如下内容:

代码语言:javascript
复制
      (slot-value vehicle 'speed)        ; gets the speed
(setf (slot-value vehicle 'speed) 100)   ; sets the speed

如果没有SETF,我们需要一个具有其名称的setter函数:

代码语言:javascript
复制
(set-slot-value vehicle 'speed 100)      ; sets the speed

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

代码语言:javascript
复制
(set-aref 3d-board 100 100 100 'foo)    ; sets the board at 100/100/100

请注意,上述setter函数可能在内部存在。但是你不需要用setf来了解他们。

结果:我们最终得到了许多不同的setter函数名。SETF机制用一种通用语法替换了所有这些机制。你知道那个电话吗?那你也认识那个策划人。它只是围绕getter调用加上新值的setf

另一个例子

代码语言:javascript
复制
      world-time                         ; may return the world time
(setf world-time (get-current-time))     ; sets the world time

等等..。

还请注意,只有宏处理设置位置:setfpushpushnewremf、.只有那些人,你才能建立一个地方。

代码语言:javascript
复制
(defun set-23 (place)
  (setf place 23))

上面可以写,但是place只是一个变量名。你不能通过一个地方。让我们重命名它,它不会改变任何事物,但会减少混淆:

代码语言:javascript
复制
(defun set-23 (foo)
  (setf foo 23))

这里,foo是一个局部变量。局部变量是一个位置。我们可以设置的东西。因此我们可以使用setf来设置变量的局部值。我们不设置被传入的东西,而是设置变量本身。

代码语言:javascript
复制
(defmethod set-24 ((vehicle audi-vehicle))
  (setf (vehicle-speed vehicle) 100))

在上面的方法中,vehicle是一个变量,它绑定到类audi-vehicle的一个对象。为了设置它的速度,我们使用setf调用编写器方法。

李斯特是从哪里认识作家的?例如,类声明生成一个:

代码语言:javascript
复制
(defclass audi-vehicle ()
   ((speed :accessor vehicle-speed)))

:accessor vehicle-speed声明导致生成读取和设置函数。

setf宏查看注册设置程序的宏展开时间。就这样。所有setf操作看起来都很相似,但是下面的Lisp知道如何设置。

下面是一些SETF使用的示例,扩展如下:

将数组项设置为索引:

代码语言:javascript
复制
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))

设置变量:

代码语言:javascript
复制
CL-USER 87 > (pprint (macroexpand-1 '(setf a 'foo)))

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

设置CLOS插槽:

代码语言:javascript
复制
CL-USER 88 > (pprint (macroexpand-1 '(setf (slot-value o1 'bar) 'foo)))

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

设置列表的第一个元素:

代码语言:javascript
复制
CL-USER 89 > (pprint (macroexpand-1 '(setf (car some-list) 'foo)))

(SYSTEM::%RPLACA SOME-LIST 'FOO)

正如您所看到的,它在扩展中使用了大量的内部代码。用户只需编写一个SETF表单,Lisp就可以计算出实际要做什么代码。

由于您可以编写自己的setter,因此只有您的想象力限制了您可能需要在以下通用语法下放置的内容:

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

Stack Overflow用户

发布于 2014-06-07 07:50:14

在你的例子中:

代码语言:javascript
复制
(defun set-23 (place)
  (setf place 23))

您不能这样做,因为您必须在上下文中使用setf。这将起作用:

代码语言:javascript
复制
(defmacro set-23 (place)
  `(setf ,place 23))

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

诀窍是,setf‘知道’如何看待它的参数来自于真实的位置,只有有限数量的函数。这些函数被称为可设置函数。

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

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

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24094927

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档