常见的lisp从功能推送

时间:2013-05-06 09:56:41

标签: lisp common-lisp

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

queuer如果第一个字段不同,则应将值line1line2推入结果中,如果第一个字段相等,则应将这两个行的聚合推送到结果中。< / p>

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

注意:我正在用(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))

感谢您的任何见解。

4 个答案:

答案 0 :(得分:7)

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

采用以下简单示例:

(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本身就是一个宏。

答案 1 :(得分:6)

如果你在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 :(得分:2)

当您调用push in queuer时,这会更改绑定“结果”的值,而不是结果指向的cons单元格。

(push x list)

基本上相当于:

(setq list (cons x list))

只要你的queuer函数是一个函数,就不可能有任何其他方式。如果使用参数“my-queue”调用它,那么当您调用函数时,该参数(符号)将被评估并且评估结果 - 一个cons单元格 - 被传递给功能。没有办法修改cons单元格以指示另一个cons单元格应该“预先”到它 - cons单元格不会跟踪指向 它们的内容。

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

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

  • 将队列包含在一个可变的间接层中。例如,您可以将车队中的队列或cons小区的cdr保留。然后,您可以在您的队列函数中变异(汽车结果)或(cdr结果),例如使用push。

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

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

(queuer-mutating data my-queue)

你会写一些像:

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

答案 3 :(得分:0)

使用data初始化(push (pop data) result)变量时,项从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"))