【问题标题】:Specification of consescons的规格
【发布时间】:2018-09-07 00:59:51
【问题描述】:

关于car and cdr 的维基百科页面说cons 是一对指针。

以下代码似乎证实了这一点:

(progn
  (setq a '(1 . 2))
  (setq b a)
  (setf (car b) 10)
  (print a))

对该表单的评估给出了缺点(10 . 2)。设置 bcar 会更改 acar。您可以在online repl at compileonline.com 中尝试。

Common Lisp specification 中定义的行为在哪里?

(我已经阅读了一些文本,但找不到指出该行为的部分。)

有趣的是,conses 上的维基百科页面说“cons 构造了包含两个 或指向值的指针的内存对象”。如果原子 1 直接存储在 cons 对象中,那么更改 b 不会更改 a,对吗?

我在上面假设 ab 持有 cons 对象而不是指向 conses 的指针。 即使实际的 lisp 实现使用指针,这也不应该在 repl 级别上可见,还是应该根据规范?当假设ab 持有指向同一个cons 的指针时,可以实现类似的效果。 Consing,即通过重复应用cons构建列表,支持conses由符号值中的指针表示的假设。

以下两种形式是等价的:

(let ((a '(1)))
  (setq b (cons 2 a)))
(setq b '(2 1))

【问题讨论】:

  • C 是传值语言,CL 是传值语言。这意味着在应用函数时,用作参数的表达式不再是表达式(或变量),而是绑定到该函数中的名称的值。这些值是地址指针与它无关。也许您应该查看按名称传递,看看它与按值传递有何不同。
  • @Sylwester 指针只是描述效果的辅助手段,我假设如果维基百科的作者可以使用,那意味着我也可以使用它。

标签: common-lisp


【解决方案1】:

设置b的车会改变a的车。

您没有设置bcar。您正在设置 ba 引用的同一 cons 单元格的 car

CL-USER 1 > (let (a b)
              (setq a (cons 1  2))
              (setq b a)

              (eq a b))
T

解释:

  • 我们有变量ab
  • (cons 1 2) 返回一个 cons 单元格
  • (setq a (cons 1 2)) 将 a 设置为 (cons 1 2) 的结果,这是一个 cons 单元格。
  • (setq b a) 计算 a,它返回上面的 cons 单元格并将 b 设置为该 cons 单元格。

理解的关键是变量和函数的求值返回非原始(即原始数字、字符等除外)对象本身,而不是副本。

我在上面假设 a 和 b 持有 cons 对象而不是指向 conses 的指针。

错了。 ab 是变量,它们都指向同一个 cons 单元格。

“cons 构造了保存两个值或指向值的指针的内存对象”

在 Common Lisp 实现中,诸如小数字(fixnums)之类的东西可能直接存储在 cons 单元中。无法通过身份可靠地比较数字(使用EQ)并且必须进行数字比较(EQL=,...)。

OTOH,cons 单元格不能存储在 cons 单元格中,因此在内部由指针引用。

你使用的操作:

  • SETQ : 第一个 form1 被求值,结果存储在变量 var1 中。 -> 注意它是怎么说的:result 而不是 结果的副本

  • RPLCA - 这是(setf CAR) 实际使用的。 : rplaca 用 object 替换了 cons 的 car 并且 cons 被修改了 -> 因此它修改了它作为参数传递的 cons 对象。

    李>
  • Evaluation - 由于您执行代码,因此适用评估规则。

进一步了解 Lisp 的执行模型:

  • 一本古老的 Lisp 书籍:John Allen 的“LISP 剖析”。 (amazon)
  • R5RS 这样的 Scheme 规范。
  • Lisp in small pieces,一本解释 Scheme/Lisp 执行模型及其实现的书。

【讨论】:

  • 谢谢。你说的对。短语“设置bcar 更改acar。”是一个宽松的公式。我的意思是设置a 的值的car 会更改b 的值的car。由于我们正在谈论语言规范,因此我应该使用正确的语言。我会在这方面改进这个问题。恕我直言,在讨论某些应用程序的实现时,很少(从不?)使用严格正确的版本“a 的汽车”。
  • 关于:“\”我在上面假设 a 和 b 持有 cons 对象而不是指向 conses 的指针。\“这是错误的。a 和 b 是变量,它们只指向同一个单一的 cons细胞。”那一点答案接近于实际问题:“Common Lisp 规范中定义的行为在哪里?” (如果您无法访问指针,则存储为值的 cons 的双指针版本等效于存储的指向两个值的 cons 的指针。但这是实现细节。)提到指针只是为了描述效果。规范中效果的定义在哪里?
  • @Tobias:具体有什么影响?您对哪个操作感兴趣?
  • 在每个实现中,变量的值都是机器整体状态的一部分。所以分配b 的值a 总是涉及以某种形式复制。这样一来,Barmer 的答案就过于简单化了。例如,在 C 中分配定义为 int b; 的变量 b 时,a 的值(也具有 int 类型)会将位模式从 a 的存储空间复制到 b 的存储空间。即便如此,这是按价值传递的。假设car 将被实现为a 的较高位,而cdr 作为较低的位然后修改(car b) 不会修改(car a)
  • C 有不同的评估模型。 Lisp 不是面向机器的。将 A 的值分配给 B 在 Lisp 中意味着:将 B 绑定到 A 的值。没有谈论内存、指针、复制、位模式、存储……。 Lisp 使用绑定到值的模型。 '假设 car 将被实现为 ' -> 的更高位,这是没有意义的,因为在 Lisp 中 a 有一个绑定,但没有位。 a 没有数据存储空间。
【解决方案2】:

首先,您的代码无效,因为不允许您修改常量列表。应该是:

(progn
  (setq a (cons 1  2))
  (setq b a)
  (setf (car b) 10)
  (print a))

当您执行(setq b a) 之类的赋值时,它会将b 的值设置为与a 的值相同。 SETQ 的规范没有说明复制该值,因此这两个变量包含 same 值,在本例中是一个 cons 单元格。

设置该 cons 单元的 car 会修改该对象——同样,没有进行复制。因此,您将通过引用 cons 单元格的任何变量或任何其他引用(它可能位于结构槽、数组元素、另一个 cons 单元格等)中看到更改。

我认为规范实际上并没有说这些东西都是一样的,它只是隐含在我们传递抽象对象的事实中,除非你调用一个函数,否则不会进行复制明确定义这样做(例如COPY-TREE)。

规范没有讨论指针,但这通常是在幕后发生的事情。一个 cons 单元就像一个 C 结构:

typedef struct cons {
    lisp_object car,
    lisp_object cdr
} cons;

lisp_object 可能是各种类型的联合(一些直接类型用于FIXNUM,以及其他类型的指针)。当一个变量包含cons时,它实际上包含了一个指向上述结构的指针,并且赋值复制的是指针,而不是结构。所以 Lisp 代码类似于 C 代码:

cons *a = make_cons(1, 2);
cons *b = a;
b->car = 10;
printf("%d\n", a->car);

【讨论】:

  • 关于您的“首先”:我来自代码完全有效的 elisp 角落(AFAIK)。为了确保 common lisp 以相同的方式处理代码,我在compileonline 使用 GNU CLisp v2.49+ 进行了尝试。它完全按照我的预期评估代码,没有错误或警告。阅读您的答案后,我在网上搜索了 GNU CLisp 命令行开关,这些开关会增加警告级别而没有运气。既然这个问题只是次要战场,我会尽快改正的。
  • @Tobias:在某些实现中执行示例代码并不是探索“未定义行为”的一种方式 - 特定实现可能在您执行它的方式中偶然具有该行为,但在其他实现和其他情况下,它的行为可能会有所不同。 “不允许修改常量列表”可能不是解释它的措辞。更好:'修改常量列表的各种影响是未定义的'。由此可见,我们不应该修改常量列表,因为我们不知道在某些情况下这样做会发生什么。
  • @Tobias:修改数据常量的可能影响:它按预期工作,其他数据也被修改,程序检测到它并抛出错误,程序崩溃,...... - 在 Lisp 中相对常见我们看到了“其他数据也被修改”的效果,因为 Common Lisp 文件编译器可以通过在检测到文字数据等于其他文字数据或其中的一部分时共享文字数据来优化空间......跨度>
  • 关于“首先”:我应该更加小心。在你的提示之后我在elisp-manual 中找到:“一个常见的陷阱是使用带引号的常量列表作为‘nconc’的非最后一个参数。如果你这样做,你的程序每次运行都会改变!”连同一个示例程序,其中使用的常量列表发生了变化。 (使用symbol-function 检测到。)谢谢,...一直在学习...
  • 请参阅 here,了解 Rainer 提到的陷阱的示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-27
  • 1970-01-01
  • 2018-11-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多