简短回答:
mapcan
具有破坏性。
根据超规格条目开启quote http://www.lispworks.com/documentation/HyperSpec/Body/s_quote.htm,
“后果是不明确的 https://en.wikipedia.org/wiki/Undefined_behavior如果文字对象(包括引用的对象)被破坏性修改。”
解决此问题的最简单方法是不使用mapcan
.
(defun subAll (l k d)
(cond
((and (atom l) (eq l k))
d)
((and (atom l) (not (eq l k)))
(cons l '()))
(t (cons
(loop for elem in l append (subAll elem k d))
'()))))
有了这个定义,
CL-USER> (suball '(1 2 3 (4 2 (3 2 (2)))) '2 '(99 98))
((1 99 98 3 (4 99 98 (3 99 98 (99 98)))))
CL-USER>
长答案:
让我先解决一些风格问题。
Please 正确格式化您的代码 http://dept-info.labri.fr/~idurand/enseignement/lst-info/PFS/Common/Strandh-Tutorial/indentation.html。它会让您和试图帮助您的人更容易阅读。
(defun subAll (l k d)
(cond
((and (atom l) (eq l k))
d)
((and (atom l) (not (eq l k)))
(cons l '()))
(t (cons
(mapcan #'(lambda (l) (subAll l k d)) l)
'()))))
接下来,Common Lisp 的标准命名风格是train-case
. Also, (cons foo '())
, (cons foo nil)
and (list foo)
都是等价的。您也可以使用最短的。 (你也不need尖锐引用lambda
形式,尽管它并没有特别伤害)。
(defun sub-all (l k d)
(cond
((and (atom l) (eq l k))
d)
((atom l)
(list l))
(t (list (mapcan #'(lambda (l) (sub-all l k d)) l)))))
让我们看一下您的函数在此期间运行时发生了什么stack overflow
case.
; SLIME 2013-04-02
CL-USER> (defun sub-all (l k d)
(cond
((and (atom l) (eq l k))
d)
((atom l)
(list l))
(t (list (mapcan #'(lambda (l) (sub-all l k d)) l)))))
;Compiler warnings :
; In an anonymous lambda form inside SUB-ALL: Undefined function SUB-ALL
SUB-ALL
CL-USER> (trace sub-all)
NIL
CL-USER> (sub-all '(1 2 3 (4 2 (3 2 (2)))) '2 '(99 98))
0> Calling (SUB-ALL (1 2 3 (4 2 (3 2 (2)))) 2 (99 98))
1> Calling (SUB-ALL 1 2 (99 98))
<1 SUB-ALL returned (1)
1> Calling (SUB-ALL 2 2 (99 98))
<1 SUB-ALL returned (99 98)
1> Calling (SUB-ALL 3 2 (99 98))
<1 SUB-ALL returned (3)
1> Calling (SUB-ALL (4 2 (3 2 (2))) 2 (99 98 3))
2> Calling (SUB-ALL 4 2 (99 98 3))
<2 SUB-ALL returned (4)
2> Calling (SUB-ALL 2 2 (99 98 3))
<2 SUB-ALL returned (99 98 3)
2> Calling (SUB-ALL (3 2 (2)) 2 (99 98 3))
3> Calling (SUB-ALL 3 2 (99 98 3))
<3 SUB-ALL returned (3)
3> Calling (SUB-ALL 2 2 (99 98 3))
<3 SUB-ALL returned (99 98 3)
3> Calling (SUB-ALL (2) 2 (99 98 3))
4> Calling (SUB-ALL 2 2 (99 98 3))
<4 SUB-ALL returned (99 98 3)
<3 SUB-ALL returned ((99 98 3))
<2 SUB-ALL returned ((3 99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 (99 98 3 ...
您实际上永远不会看到关键突变点,但您可以看到早期的等效点(请注意,通过调用的第二“层”,d
is (99 98 3)
,而不是(99 98)
您最初传入的)。在那之后不久的某个时刻,d
变成(99 98 3 (2))
,此时循环将无限进行,因为您可以在替换对象内找到目标。当我需要时我通常会做什么mapcan
正在定义我自己的功能版本。
(defun mappend (fn list)
(loop for elem in list
append (funcall fn elem)))
(defun sub-all (tree target replacement)
(cond
((and (atom tree) (eq tree target))
replacement)
((atom tree)
(list tree))
(t (list
(mappend
(lambda (sub)
(sub-all sub target replacement))
tree)))))
这也解决了引用列表的未定义行为。具体来说,根据上面的定义mappend
,
CL-USER> (mappend #'(lambda (l) '(1 2)) '(a b))
; in: MAPPEND #'(LAMBDA (L) '(1 2))
; #'(LAMBDA (L) '(1 2))
;
; caught STYLE-WARNING:
; The variable L is defined but never used.
;
; compilation unit finished
; caught 1 STYLE-WARNING condition
(1 2 1 2)
CL-USER> (mappend #'(lambda (l) (declare (ignore l)) '(1 2)) '(a b))
(1 2 1 2)
CL-USER>
查看这个答案 https://stackoverflow.com/a/18790523/190887(上面已由 Joshua Taylor 链接)了解更多详细信息。