n-6ren">
gpt4 book ai didi

lisp - 试图理解 setf + aref "magic"

转载 作者:太空宇宙 更新时间:2023-11-03 18:37:25 29 4
gpt4 key购买 nike

我现在已经了解了数组和 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 is a constant, may not be used as a variable

(这绝对有道理!)

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

  1. 我的示例中发生了什么,这不会导致错误,为什么它什么都不做?
  2. 我该如何解决这个问题才能使我的 set-23职能工作?

我还想为此使用一个 thunk 来延迟执行 aref ,就像:

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

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

(SETF FUNCALL) is only defined for functions of the form #'symbol.

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

PS:在“Land of Lisp”(我目前正在阅读以了解 Lisp)中,它说:

In fact, the first argument in setf is a special sublanguage of Common Lisp, called a generalized reference. Not every Lisp command is allowed in a generalized reference, but you can still put in some pretty complicated stuff: […]

好吧,我想这就是原因(或者至少是原因之一),为什么所有这些都没有像我预期的那样工作,但我还是很想了解更多信息:-)

最佳答案

地点 不是物理,它只是我们可以获取/设置值的任何事物的概念。所以 place 通常不能返回或传递。 Lisp 开发人员想要一种仅通过知道 getter 是什么就可以轻松猜出 setter 的方法。所以我们写了 getter,用一个包围的 setf 形式,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 电话吗?那么你也知道二传手。它只是围绕 getter 调用加上新值的 setf

另一个例子

      world-time                         ; may return the world time
(setf world-time (get-current-time)) ; sets the world time

等等……

另请注意,只有宏处理设置位置:setfpushpushnewremf、. .. 只有那些你可以设置一个地方。

(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,因此只有您的想象力会限制您可能想要在这种通用语法下放置的内容:

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

关于lisp - 试图理解 setf + aref "magic",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24094927/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com