试图了解setf + aref的“魔力" [英] Trying to understand setf + aref "magic"

查看:83
本文介绍了试图了解setf + aref的“魔力"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I now have learnt about arrays and aref in Lisp. So far, it's quite easy to grasp, and it works like a charme:

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

令我困惑的是当您将arefsetf结合使用时发生的aref魔术".似乎aref知道其调用上下文,然后将决定是否返回一个值或setf可以使用的位置.

What puzzles me is the aref "magic" that happens when you combine aref and setf. It seems as if aref knew about its calling context, and would then decide whether to return a value or a place that can be used by setf.

无论如何,就目前而言,我只是认为这是理所当然的,不要考虑它在内部的工作方式过多.

Anyway, for the moment I just take this as granted, and don't think about the way this works internally too much.

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

But now I wanted to create a function that sets an element of the *foo* array to a predefined value, but I don't want to hardcode the *foo* array, instead I want to hand over a place:

(defun set-23 (place)
  …)

因此,基本上,此功能将 place 设置为23,无论位于何处.我最初的幼稚方法是

So basically this function sets place to 23, whatever place is. My initial naive approach was

(defun set-23 (place)
  (setf place 23))

并使用以下命令调用它:

and call it using:

(set-23 (aref *foo* 0))

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

This does not result in an error, but it also doesn't change *foo* at all. My guess would be that the call to aref resolves to nil (as the array is currently empty), so this would mean that

(setf nil 23)

正在运行,但是当我在REPL中手动尝试此操作时,出现一条错误消息告诉我:

is run, but when I try this manually in the REPL, I get an error telling me that:

NIL是一个常量,不能用作变量

NIL is a constant, may not be used as a variable

(这绝对有道理!)

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

So, finally I have two questions:

  1. 我的样本中发生了什么,这不会导致错误,为什么它什么也没做?
  2. 如何解决此问题以使我的set-23函数正常工作?
  1. What happens in my sample, and what does this not cause an error, and why doesn't it do anything?
  2. How could I solve this to make my set-23 function work?

我也有想法为此使用thunk来延迟aref的执行,就像:

I also had the idea to use a thunk for this to defer execution of aref, just like:

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

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

But this already runs into an error when I try to define this function, as Lisp now tells me:

(SETF FUNCALL)仅针对形式为#'symbol的函数定义.

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

再次,我想知道为什么会这样.为什么将setffuncall结合使用显然对命名函数有效,但不适用于lambda,例如?

Again, I wonder why this is. Why does using setf in combination with funcall apparently work for named functions, but not for lambdas, e.g.?

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

PS: In "Land of Lisp" (which I'm currently reading to learn about Lisp) it says:

实际上,setf中的第一个参数是Common 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: […]

好吧,我想这就是这里的原因(或者至少是原因之一),为什么所有这些都不如我所期望的那样起作用,但是尽管如此,我还是想知道更多:-)

Well, I guess that this is the reason (or at least one of the reasons) here, why all this does not work as I'd expect it, but nevertheless I'm curious to learn more :-)

推荐答案

一个 place 并不是 physical ,它只是任何我们可以获取/设置一个对象的概念价值.因此,通常不能返回或传递 place . Lisp开发人员希望有一种方法,可以通过仅知道吸气剂是什么就可以轻松猜出二传手.因此,我们编写了一个带有周围setf形式的吸气剂,Lisp指出了如何进行设置:

A place is nothing physical, it's just a concept for anything where we can get/set a value. So a place in general can't be returned or passed. Lisp developers wanted a way to easily guess a setter from just knowing what the getter is. So we write the getter, with a surrounding setf form and Lisp figures out how to set something:

      (slot-value vehicle 'speed)        ; gets the speed
(setf (slot-value vehicle 'speed) 100)   ; sets the speed

如果没有SETF,我们将需要一个名为setter的函数:

Without SETF we would need a setter function with its name:

(set-slot-value vehicle 'speed 100)      ; sets the speed

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

For setting an array we would need another function name:

(set-aref 3d-board 100 100 100 'foo)    ; sets the board at 100/100/100

请注意,上述设置器功能可能在内部存在.但是您不需要通过setf了解它们.

Note that the above setter functions might exist internally. But you don't need to know them with setf.

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

Result: we end up with a multitude of different setter function names. The SETF mechanism replaces ALL of them with one common syntax. You know the getter call? Then you know the setter, too. It's just setf around the getter call plus the new value.

另一个例子

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

依此类推...

还请注意,只有宏才能处理设置位置:setfpushpushnewremf,... ...只有那些宏才可以设置位置.

Note also that only macros deal with setting places: setf, push, pushnew, remf, ... Only with those you can set a place.

(defun set-23 (place)
  (setf place 23))

上面可以写

,但是place只是一个变量名.你不能通过一个地方.让我们重命名它,它不会改变任何事情,但是会减少混乱:

Above can be written, but place is just a variable name. You can't pass a place. Let's rename it, which does not change a thing, but reduces confusion:

(defun set-23 (foo)
  (setf foo 23))

此处foo是局部变量.局部变量是 place .我们可以设置的东西.因此,我们可以使用setf设置变量的局部值.我们不设置传递的内容,而是设置变量本身.

Here foo is a local variable. A local variable is a place. Something we can set. So we can use setf to set the local value of the variable. We don't set something that gets passed in, we set the variable itself.

(defmethod set-24 ((vehicle audi-vehicle))
  (setf (vehicle-speed vehicle) 100))

在上述方法中,vehicle是变量,并且绑定到类audi-vehicle的对象.要设置速度,我们使用setf调用writer方法.

In above method, vehicle is a variable and it is bound to an object of class audi-vehicle. To set the speed of it, we use setf to call the writer method.

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

Where does Lisp know the writer from? For example a class declaration generates one:

(defclass audi-vehicle ()
   ((speed :accessor vehicle-speed)))

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

The :accessor vehicle-speed declaration causes both reading and setting functions to be generated.

setf宏查看已注册设置器的宏扩展时间.就这样.所有setf操作看起来都相似,但是下面的Lisp知道如何设置.

The setf macro looks at macro expansion time for the registered setter. That's all. All setf operations look similar, but Lisp underneath knows how to set things.

以下是SETF用途的一些示例,已扩展:

Here are some examples for SETF uses, expanded:

在索引处设置数组项:

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)

设置列表的第一个元素:

Setting the first element of a list:

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

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

如您所见,它在扩展中使用了很多内部代码.用户只需编写一个SETF表单,Lisp便会找出实际执行此操作的代码.

As you can see it uses a lot of internal code in the expansion. The user just writes a SETF form and Lisp figures out what code would actually do the thing.

由于您可以编写自己的二传手,所以只有您的想象力才能限制您可能希望使用以下通用语法编写的内容:

Since you can write your own setter, only your imagination limits the things you might want to put under this common syntax:

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

这篇关于试图了解setf + aref的“魔力"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆