我有以下常见的lisp函数:(aggregate line1 line2)
和(queuer data result)
。
queuer
如果第一个字段不同,则应将值line1
和line2
推入结果中,如果第一个字段相等,则应将这两个行的聚合推送到结果中。< / 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))
感谢您的任何见解。
答案 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)
。请注意,这不会影响外部的foo
。 Foo
仍然指向
和以前一样。
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"))