Search code examples
emacselisp

Emacs list is not a list


This code evaluates to t

(listp '(foo 0 . 0))

These code gives an error: eval: Wrong type argument: listp, 0

(mapcar (lambda (x) x) '(foo 0 . 0))
(length '(foo 0 . 0))

All three are using the same "list", but mapcar and length clearly do not think that it is a list. This is because the list ends with a 0 . 0 rather than 0 0, although I do not know why that makes a difference to mapcar but not listp.

Is there a way to modify the mapcar expression to accept both regular lists like (foo 0 0) and these cons-style lists (foo 0 . 0)? In the actually application, my input has both types of lists (e.g., ("a" (b 0 . 0) (c 0 . 0)) and the lambda is a recursive function that calls mapcar if its argument is a list.

(In case the previous paragraph was not clear, the answer "use (foo 0 0) instead" is wrong.)

I suspect the answer is something like

(defun my-func (x) 
  (if (consp x)
    (if (not-actually-a-list-p x)
      (delistify (mapcar #'myfunc (listify x)))
     (mapcar #'myfunc input))
   ; process the individual atoms
   ))

However, I do not know what not-actually-a-list-p, listify and delistify should be.


Solution

  • The reason listp returns T is because it only checks whether the argument is either nil or a cons-cell. A proper list is either nil or one where all cdr satisfy listp.

    mapcar and length actually have to iterate the list and choke on improper lists, because they cannot take the cdr of something not being a cons-cell.

    You only need to implement mapcar-dot to solve your problem. For example, here is a recursive approach:

    (defun mapcar-dot (f list)
      (typecase list
        (null nil)
        (cons (cons (funcall f (car list))
                    (mapcar-dot f (cdr list))))
        (t (funcall f list))))
    

    Example

    (list (mapcar-dot #'1+ '(1 2 3 4))
          (mapcar-dot #'1+ '(1 2 3 . 4)))
    => ((2 3 4 5) (2 3 4 . 5))
    

    Here, I preserve the original structure, meaning that improper lists as inputs give improper lists as outputs. I don't know if this is what you want. In case you want to always return proper lists, then simply do:

    (defun mapcar-dot* (f list)
      (loop for (a . b) on list
            collect (funcall f a)
            unless (listp b)
              collect (funcall f b)))