【问题标题】:In Common Lisp, when are objects referenced and when are they directly accessed by value?在 Common Lisp 中,什么时候引用对象,什么时候直接按值访问?
【发布时间】:2019-01-03 07:31:00
【问题描述】:

我正在阅读 this question 以尝试深入了解答案。它专门询问传递引用,所有答案似乎都表明不支持传递引用。但是,this answer 暗示虽然可能不支持通过引用传递,但确实可以通过引用访问某些值。一个更简单的示例将涉及 cons 单元格;我可以将一个 cons 单元格传递给一个函数,然后将它的 cdr 或 car 更改为我喜欢的任何值。

最终我想知道(使用 C# 用语)值类型和引用类型之间是否有明确的界限,以及是否有任何方法(比上面引用的答案更方便)将值视为引用类型。

【问题讨论】:

  • 拥有可变值的最简单方法是将其装箱,(setf boxed_val (list val)),然后在boxed_val 上使用RPLACA,即使在将其作为参数接收的函数中也是如此。当然,只需使用 CAR 来获取值本身。

标签: reference lisp common-lisp


【解决方案1】:

没有区别:所有对象在 Lisp 中都是按值传递的(至少在我所知道的所有 Lisp 中)。 然而有些对象是可变的,conses就是这样一种类型。因此,您可以将一个 cons 单元格传递给一个过程并在该过程中对其进行变异。因此,重要的考虑因素是对象是否可变。

特别是这个 (Common Lisp) 函数总是返回 T 作为它的第一个值,即使它的第二个值可能没有 0 作为它的 car 或 cdr。

(defun cbv (&optional (f #'identity))
  (let ((c (cons 0 0)))
    (let ((cc c))
      (funcall f c)
      (values (eq c cc) c))))

> (cbv (lambda (c)
         (setf (car c) 1
               (cdr c) 2)))
t
(1 . 2)

但是,由于 Common Lisp 具有词法范围、一流的函数和宏,您可以做一些诡计,使其看起来有点像引用调用:

(defmacro capture-binding (var)
  ;; Construct an object which captures a binding
  `(lambda (&optional (new-val nil new-val-p))
     (when new-val-p
       (setf ,var new-val))
     ,var))

(defun captured-binding-value (cb)
  ;; value of a captured binding
  (funcall cb))

(defun (setf captured-binding-value) (new cb)
  ;; change the value of a captured binding
  (funcall cb new))

(defun cbd (&optional (f #'identity))
  (let ((c (cons 0 0)))
    (let ((cc c))
      (funcall f (capture-binding c))
      (values (eq c cc) c cc))))

现在:

> (cbd (lambda (b)
         (setf (captured-binding-value b) 3)))
nil
3
(0 . 0)

如果您了解它的工作原理,您可能对 Lisp 中作用域和宏的工作原理了解很多。


在 Common Lisp 中通过值传递对象的普遍性有一个例外,Rainer 在下面的评论中提到:为了提高效率,某些原始类型的实例可能会在某些情况下被复制。这只会发生在特定类型的实例上,并且发生它的对象总是不可变的。为了处理这种情况,CL 提供了一个相等谓词eql,它与eq 做同样的事情,除了它知道可以以这种方式秘密复制的对象并正确比较它们.

因此,安全的做法是使用eql 而不是eq:因为可以复制的对象始终是不可变的,这意味着您永远不会被它绊倒。

这是一个示例,其中您自然会认为相同的对象结果并非如此。鉴于此定义:

(defun compare (a b)
  (values (eq a b)
          (eql a b)))

然后在我正在使用的实现中,我发现:

> (compare 1.0d0 1.0d0)
nil
t

所以双精度浮点零对于它自己始终不是eq,但对于它自己始终是eql。并尝试一些看起来应该相同的东西:

> (let ((x 1.0d0)) (compare x x))
t
t

所以在这种情况下,函数调用似乎不是在复制对象,而是我从来自阅读器的两个不同对象开始。但是,始终允许实现随意​​复制数字,并且可以通过不同的优化设置来实现。

【讨论】:

  • 注意:一些原始参数(一些数字、字符等)可能会被复制。不复制其他参数。局部变量只是对对象的另一个引用。
  • @RainerJoswig:我添加了一条关于复制的注释,我认为这是正确的:如果不是,请随时编辑!
  • 好的,所以我理解您关于某些值被复制而其他值未复制与未复制以用于优化目的的观点。我也理解使用闭包来模拟引用创建的代码示例。然而,我仍然对第一点感到困惑。 C 和 CC 是 EQ,因为它们是同一个对象。在我的测试中,编辑 C 似乎对 CC 产生了相同的影响(因为它们是同一个对象)。这将向我表明(尽管我在这里的假设可能不正确)对象本身正在被 C 或 CC 中的至少一个通过引用访问。这不正确吗?
  • ccc 绑定只是将 相同的对象 作为它们的值:两者都不是任何类型的引用,因为 Lisp 是按值调用的。您可能想看看我谈论绑定的this previous answer of mine,也许还有this article(与答案相同)然后this article,这是我作为这个问题的结果写的。
  • 考虑复杂的结构会使问题变得混乱。按值表示您仅传输绑定的值,而没有关于该值来源的其他信息。您无法更改不在范围内的绑定。当然,一个值可以是其他值的存储,在这种情况下,如果对象是可变的,您就有一种引用,但这不是重点。 By-value 也不意味着或与复制机制相矛盾。
猜你喜欢
  • 1970-01-01
  • 2011-03-26
  • 1970-01-01
  • 2017-04-15
  • 2011-09-28
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 2012-08-07
相关资源
最近更新 更多