我现在已经了解了Lisp中的数组和aref。到目前为止,它很容易掌握,并且像一个烟囱一样工作:

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

使我困惑的是当您将arefaref结合使用时发生的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现在告诉我:



    同样,我想知道为什么会这样。为什么将setffuncall结合使用显然对命名函数有效,但不适用于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
    

    等等...

    还要注意,只有宏才能处理设置位置: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/

    10-09 08:35