sábado, 21 de julio de 2012

When mapcan backfires

While writing some elisp for clasker (still alpha), The destructive function mapcan backfired in a really strange way.

Calling mapcan on a list of really simple functions that just returned lists, worked ok, but just the first time.

The issue is a combination of different things, mainly Iterating through functions that return quoted lists.  When nconc does its thing, it uses the lists in place, so we're indeed modifying the defuns.

Here's the small code that exemplifies the issue, and a possible way to avoid it.

ELISP> (defun t1 () '("hola"))
t1
ELISP> (defun t2 () (list "adeu"))
t2
ELISP> (t1) 
("hola")

ELISP> (t2)
("adeu")

ELISP> (mapcan 'funcall '(t1 t2))
("hola" "adeu")

ELISP> (t1)
("hola" "adeu") ; => Oops, t1 has been modified

ELISP> (mapcan 'funcall '(t2 t1))
("adeu" "hola" "adeu")

ELISP> (t2)
("adeu")

ELISP> (defun t1 () '("hola")) ; Redefine t1 and t2 for sanity assurance
t1
ELISP> (defun t2 () (list "adeu"))
t2
ELISP> (mapcan 'funcall '(t2 t1))
("adeu" "hola")

ELISP> (t2) 
("adeu") ; => t2 is fine

Generating the lists with the (list ...) function solves the issue, because a new object is created every time, so we're not returning the literal object from the source.

Also,  (reduce 'append (mapcar ...)) will do "the right thing" (tm)