【问题标题】:Why I can't (push 3 '()) in Common Lisp's REPL?为什么我不能 (push 3 '()) 在 Common Lisp 的 REPL 中?
【发布时间】:2021-09-25 16:14:12
【问题描述】:

我在 Emacs 中使用 Common Lisp 和 Slime。与使用它相比,我有更多避免突变的经验。

在 REPL 中我可以做到:

CL-USER> (defvar so-example '())
SO-EXAMPLE

CL-USER> (push 7 so-example)
(7)

而且它有效。但是,如果我尝试:

CL-USER> (push 7 '())

我收到一条错误消息:

未定义函数:(SETF QUOTE)

好的。既然quote有问题,我也试过了:

CL-USER> (push 7 nil)

这也会引发错误消息:

NIL 是一个常数,因此不能设置。

为什么会这样?为什么有意义?

对我来说,这很奇怪。

【问题讨论】:

    标签: common-lisp read-eval-print-loop


    【解决方案1】:

    修改文字常量会导致 Common Lisp 中未定义的行为; from the HyperSpec:

    如果文字对象(包括引用对象)被破坏性修改,后果是不确定的。

    这解释了使用(push 7 nil) 观察到的错误消息; (push 7 ()) 会出现同样的错误消息。

    表达式(push 7 '()) 等价于(setf '() (cons 7 '()))setf 需要 place,但 (quote ()) 不是一个地方。当setf 宏意识到quote 无法生成可设置的位置时,它会发现错误。

    请注意,according to the documentationpush 需要一个 item 和一个 place(不是 list):“@ 987654333@ 将项目添加到存储在适当位置的列表中......”。也就是说,place 是对列表的引用,而不是列表本身。

    一般来说,尝试像这样改变文字常量没有多大意义。考虑用数字来代替。试图通过修改数字 7 将 7 更改为 8 并不是我们想要做的。相反,我们将建立一个 binding 到 7,并改变绑定。对空列表做同样的事情:

    CL-USER> (defvar empty-list '())
    SO-EXAMPLE
    CL-USER> (setf empty-list (cons 7 empty-list))
    (7)
    

    这里建立了到空列表的绑定 (empty-list),然后 绑定 发生突变,将 empty-list 绑定到通过将 7() 组合创建的新列表。这正是第一个发布的示例中发生的情况。

    CL-USER> (defvar so-example '())
    SO-EXAMPLE
    CL-USER> (push 7 so-example)
    (7)
    

    使用算术的类似示例可能是:

    CL-USER> (defvar x 7)
    X
    CL-USER> (setf x (+ 1 x))
    8
    CL-USER> x
    8
    

    但您可能不会期望 (setf 7 (+ 1 7)) 做任何好事。

    【讨论】:

      【解决方案2】:

      push 不能将文字用作第二个参数,因为它应该改变第二个参数指示的位置。空列表没有位置,不能重新定义。

      push 是一个宏,它为不同外观的第二个参数做不同的事情。您可以通过查看maroexpand-1 来检查它的作用:

      (macroexpand-1 '(push 5 binding))
      ; ==> (set1 binding (cons 5 binding))
      
      (macroexpand-1 '(push 5 (car structure)))
      ; ==> (let* ((tmp structure))
      ;       (rplaca tmp (cons 5 (car tmp))))
      

      它如何知道该做什么是通过查看setf 函数。例如。调用:

      (get-setf-expansion 'binding)
      ; ==> () 
      ;     ()
      ;     (tmp)
      ;     (setq binding tmp)
      ;     binding
      

      您可以定义自己的,例如。一个类,这样您就可以将push 元素添加到您创建的类的对象中,并使其做一些特别的事情。例如。您正在扩展 push 的语言功能,使其也支持您创建的类。

      那么当我评估(get-setf-expansion ''()) 时会发生什么,你知道(macroexpand-1 '(push 3 '()) 的扩展变成:

      (let* ((tmp (cons 3 '())))
        (funcall #'(setf quote) tmp '())) 
       
      

      还有你的异常错误消息的来源。

      【讨论】:

        【解决方案3】:

        PUSH 的文档说:

        push item place => new-place-value
        

        位置通用引用:变量、数组槽、结构槽、CLOS 对象槽等等。它们记录在这里:CLHS 5.1 Generalized Reference

        泛化是什么意思?在 Common Lisp 中,places 是一个超越变量或槽等简单引用的概念。它甚至是用户可扩展的 -> 可以定义新类型的地点

        NIL 作为一个地方

        NIL(与() 相同)记录为常量变量

        因此这样的事情失败了:

        (setf nil 10)
        

        (push 10 nil) 正在尝试将10 推送到地点 NIL

        CL-USER 1 > (macroexpand-1 '(push 10 nil))
        (LET ((#:|new-value-1070| 10))
          (LET* ((#:|Store-Var-1069| (CONS #:|new-value-1070| NIL)))
            (SETQ NIL #:|Store-Var-1069|)))
        

        有它:尝试将NIL(作为参考,这里是一个变量)设置为值 10。

        由于NIL被定义为一个常量变量并且常量变量是不可改变的,所以不能把东西推到nil的地方。

        因此NIL 不是一个有用的地方

        'NIL 作为一个地方

        为什么(push 10 '()) 会失败?与(push 10 'nil)(push 10 (quote nil)) 相同。

        同样,push 需要一个位置。 (quote ...) 不是一个定义的地方。

        这里定义了默认定义的地方:CLHS 5.1.2 Kinds of Places

        这是有道理的,因为'() 没有引用任何内容。请记住:地点一般参考。它应该是一个文字对象()

        功能缺点

        如果我们想将新对象添加到列表中,那么我们使用cons

        CL-USER 4 > (cons 10 nil)
        (10)
        
        CL-USER 5 > (cons 10 'nil)
        (10)
        

        必须使用结果列表:将其存储在某处,将其传递给函数,...

        【讨论】:

          猜你喜欢
          • 2012-12-27
          • 2019-01-18
          • 1970-01-01
          • 1970-01-01
          • 2012-05-18
          • 2011-11-14
          • 2021-08-23
          • 2015-07-03
          • 1970-01-01
          相关资源
          最近更新 更多