Why does this mapcan cause my REPL to freeze?

2019-02-24 22:58发布

In this very useful answer, it was suggested I could replace this code:

(defun describe-paths (location edges)
  (apply (function append) (mapcar #'describe-path
               (cdr (assoc location edges)))))

With this:

(defun describe-paths-mapcan (location edges)
  (mapcan #'describe-path
               (cdr (assoc location edges))))

I certainly understand conceptually why this should work, but it doesn't; the second variation freezes my REPL and the CL prompt never returns. I have to restart SLIME. So I looked it up, and am wondering if the fact that mapcan doesn't use list, but rather nconc, is the cause? Therefore these are actually not identically functioning code blocks?

For the curious, I am passing this:

(describe-paths-mapcan 'living-room *edges*)

Where *edges* is:

(defparameter *edges* '((living-room (garden west door)
             (attic upstairs ladder))
            (garden (living-room east door))
            (attic (living-room downstairs ladder))))

And:

(defun describe-path (edge)
  `(there is a ,(caddr edge) going ,(cadr edge) from here.))

1条回答
女痞
2楼-- · 2019-02-24 23:34

I think it has to do with describe-edges. It is defined as:

(defun describe-path (edge)
  `(there is a ,(caddr edge) going ,(cadr edge) from here.))

The quasiquote there we can macroexpand.. And you get:

(macroexpand '`(there is a ,(caddr edge) going ,(cadr edge) from here.)) ; ==>
(CONS 'THERE
 (CONS 'IS
  (CONS 'A
   (CONS (CADDR EDGE) (CONS 'GOING (CONS (CADR EDGE) '(FROM HERE.)))))))

According to the documentation for mapcan the concatination is done destructively. Looking at the very last element of whats returned from describe-path will share structure with the next element it returns so nconc will make an infinite loop.

If you were to change describe-edges to the following it will work:

(defun describe-path (edge)
  (list 'there 'is 'a (caddr edge) 'going (cadr edge) 'from 'here.))
查看更多
登录 后发表回答