【问题标题】:Common lisp push from function常见的 lisp 从函数推送
【发布时间】:2013-04-30 01:25:50
【问题描述】:

我有以下常用的 lisp 函数:(aggregate line1 line2)(queuer data result)

queuer 应该将值 line1line2 推入结果,如果它们的第一个字段不同,或者这 2 行的聚合如果它们的第一个字段相等。

我不知道为什么它没有改变我的结果列表。

注意:我正在使用(push (pop data) result) 初始化结果列表,以便在其中包含第一个元素。这 2 个列表是 1 深度嵌套列表 (("1" "text") ("2" "text") (...))

(defun aggregate (line1 line2)
  (progn
    (list 
     (nth 0 line1)
     (nth 1 line1)
     (nth 2 line1)
     (concatenate 'string (nth 3 line1) ", " (nth 3 line2))
     (concatenate 'string (nth 4 line1) ", " (nth 4 line2)))))

(push (pop x) y)

(defun queuer (data result)
  (loop do
       (let ((line1 (pop data))
             (line2 (pop result)))
         (if (equal (first line1) (first line2))
             (progn
               (push (aggregate line1 line2) result)
               (print "=="))
             (progn
               (push line2 result)
               (push line1 result)
               (print "<>"))))
       while data))

感谢您提供任何见解。

【问题讨论】:

    标签: lisp common-lisp


    【解决方案1】:

    如果你用 Lisp 编写函数,最好从“函数式”的角度来思考。函数接受值并返回值。一个典型的规则是避免副作用。所以你的函数应该返回一个结果值,而不是“修改”一个变量值。

    代替:

    (defparameter *result* '())
    
    (defun foo (a)
       (push a *result*))
    

    使用:

    (defparameter *result* '())
    
    (defun foo (a result)
      (push a result)
      result)
    
    (setf *result* (foo a *result*))
    

    还要注意aggregate 不需要progn

    稍微高级(不要那样做):

    如果你有一个全局列表:

    (defparameter *foo* '())
    

    正如我们所见,你不能像这样推动它:

    (defun foo (l)
       (push 1 l))
    

    如果您调用foo,变量*foo* 将保持不变。原因:Lisp 没有传递变量引用,而是传递了变量的值。

    但是我们怎样才能传递一个引用呢?好吧,传递一个引用:一个 cons 单元会做它(或一个结构、一个向量、一个 CLOS 对象,...):

    CL-USER 38 > (defparameter *foo* (list '()))
    *FOO*
    
    CL-USER 39 > (defun foo (ref)
                   (push 1 (first ref)))
    FOO
    
    CL-USER 40 > (foo *foo*)
    (1)
    
    CL-USER 41 > (foo *foo*)
    (1 1)
    

    现在,如果我们查看*foo*,它会发生变化。但是我们并没有真正改变变量。我们更改了列表的第一个条目。

    CL-USER 42 > *foo*
    ((1 1))
    

    但是,不要这样做。以函数式风格编写程序。

    【讨论】:

      【解决方案2】:

      您不能使用仅获取变量值的函数来修改变量的内容。

      举个简单的例子:

      (defun futile-push (thing list)
        (push thing list))
      
      (let ((foo (list 1)))
        (futile-push 2 foo))
      

      会发生什么?

      • Foo 被评估为它指向的列表。
      • 2 计算结果为 2。
      • 这两个参数被传递给函数。

      函数调用内部:

      • Thing 现在绑定到 2。
      • List 现在绑定到列表 (1)

      注意,列表不知道它也被变量引用 foo 函数外。

               foo
                |
                v
              ---------
      list -> | 1 |NIL|
              ---------
      
      • Push 修改变量 list,使其现在绑定到 列表(2 1)

      请注意,这不会影响外部的fooFoo 仍然指向 和以前一样。

                           foo
                            |
                            v
              ---------   ---------
      list -> | 2 | ----> | 1 |NIL|
              ---------   ---------
      
      • Futile-push 返回push 形式的返回值,发生这种情况 成为list 的新值。

      • 该返回值从未被使用或绑定,因此它消失了。

         foo
          |
          v
        ---------
        | 1 |NIL|
        ---------
        

      做你想做的最直接的方法是返回新的 值,然后在外面设置变量:

      (let ((foo (list 1)))
        (setf foo (not-so-futile-push 2 foo)))
      

      如果您需要在多个地方这样做,那可能是值得的 为扩展为setf 表单的宏编写宏。注意 由于这些原因,push 本身就是一个宏。

      【讨论】:

        【解决方案3】:

        当您在队列中调用 push 时,这会更改 binding“结果”的值,而不是结果指向的 cons 单元格。

        (push x list)
        

        本质上等同于:

        (setq list (cons x list))
        

        只要你的队列函数是一个函数,它就不可能是其他任何方式。如果您使用参数“my-queue”调用它,那么当您调用该函数时,该参数(一个符号)将被 求值,并且求值结果(一个 cons 单元格)被传递给功能。无法修改该 cons 单元格以指示另一个 cons 单元格应“添加”到它 - cons 单元格不跟踪指向 它们的事物。

        有(至少)三种可能的解决方案:

        • 编写您的代码,以便排队器返回新队列,而不是期望参数被修改(或“变异”)。

        • 将队列包装在一个可变的间接层内。例如,您可以将队列保留在汽车中或 cons 单元的 cdr 中。然后你就可以在你的 queuer 函数中改变 (car result) 或 (cdr result),例如使用 push。

        • 将队列器转换为宏而不是函数。然后,您可以编写代码来改变其参数,无论您在何处使用 queuer 宏,该参数都将被“插入”到您的代码中。

        我个人会推荐第一个解决方案。如果你有你的变异队列,你会在哪里写:

        (queuer-mutating data my-queue)
        

        你应该写这样的东西:

        (setf my-queue (queuer-not-mutating data my-queue))
        

        【讨论】:

          【解决方案4】:

          当您使用(push (pop data) result) 初始化data 变量时,它 项从data 移动到result,而不是复制:

          CL-USER> (setq data '(("1" "text1") ("2" "text2") ("3" "text3")))
          (("1" "text1") ("2" "text2") ("3" "text3"))
          CL-USER> (setq result nil)
          NIL
          CL-USER> (push (pop data) result)
          ;Compiler warnings :
          ;   In an anonymous lambda form: Undeclared free variable DATA (3 references)
          (("1" "text1"))
          CL-USER> (print data)
          
          (("2" "text2") ("3" "text3")) 
          (("2" "text2") ("3" "text3"))
          CL-USER> (print result)
          
          (("1" "text1")) 
          (("1" "text1"))
          

          您可能想要使用 (copy-list list) 函数:

          CL-USER> (setq copy2 (copy-list data))
          (("2" "text2") ("3" "text3"))
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-12-10
            • 2020-09-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-09-13
            • 1970-01-01
            相关资源
            最近更新 更多