15

I'm really confused with the . notation. Is '(a . b) a list?

(listp '(a . b)) returns t but when I want to know its length (length '(a . b)) gives an error Wrong type argument: listp, b. The same is for other functions as nth,mapcar etc. they all give the same error

Is there any function which can I distinguish between '(a b) and '(a . b)?


Context: I encountered this problem when I wanted to implement recursive version of mapcar. Here is my implementation

(defun true-listp (object)
"Return non-`nil' if OBJECT is a true list."
(and (listp object)  (null (cdr (last object)))))

(defun recursive-mapcar (func list)
"Evaluates func on elements of the list, then on elements of elements  of the list and so forth." 
(let ((output nil))
(flet ((comp (a b) nil)
       (call-fun-and-save (x) (add-to-list 'output (funcall func x) t 'comp))
       (recursion (l)
                  (mapcar
                   (lambda (x)
                     (call-fun-and-save x)
                     (if (and (true-listp x))  ;; HERE I use true-listp, testing for list or cons is not sufficient
                         (recursion x)))
                    l)))
   (recursion list))
  output))

I use this to extract all specific tags from parsed html. Example of html to parse

;; buffer 'html'
<html>
<body>
<table style="width:100%">
  <tr>  <td>Jill</td>  <td>Smith</td>  <td>50</td> </tr>
  <tr>  <td>Eve</td>   <td>Jackson</td>   <td>94</td> </tr>
</table>
</body>
</html>

Then I extract all <td> as

(with-current-buffer (get-buffer "html")
  (let ((data (libxml-parse-html-region (point-max) (point-min))))

    ;; gat only <td> tags
    (-non-nil
     (recursive-mapcar
      (lambda(x) (and (consp x) (equal 'td (car x)) x))
      data))
    data
    )
  )
  • 1
    There is no true-list-p in Elisp simply because it hasn't been found to be useful enough to provide it. Indeed, I can't remember the last time I wanted to test if a list was proper, so maybe if you give us a bit more info about your use-case we can help you solve your problem in another way. – Stefan Jun 1 '16 at 13:59
  • @Stefan In short, I want to implement recursive mapcar, I evaluates given function on elements of a given list, then on elements of elements of the list, then on elements of elements of elements of the list and so forth. So I need to know if an element is a true list or not. – tom Jun 1 '16 at 17:34
  • It is usefull for example when I parse html with libxml-parse-html-region and I want to extract all <td> tags. – tom Jun 1 '16 at 17:36
  • Can you show us a concrete example where you might get either a proper list, or an improper list, or something else, and where you need to handle the 3 cases differently? In most cases I've had to deal with, the "proper" and "improper" cases can be shared until we get to the actual improper tail, so you again don't need to test if it's proper or not: just test if it's consp instead. – Stefan Jun 2 '16 at 1:05
  • 1
    libxml doesn't just return lists-of-lists. Each list representing an XML element has the form (symbol attributes . contents). So your code shouldn't apply mapcar recursively over all elements of the lists, only over the cddr of the list (to skip the element name and the attributes). Once you do that, you should find that all lists are proper and your problem will disappear. It will also fix a bug in your code where you may confuse a td attribute for a td element. – Stefan Jun 2 '16 at 18:18
22

It satisfies listp, so in that sense it is a list. listp just tests whether something is a cons or nil (aka ()), on the one hand, or something else, on the other hand.

A proper list or true list (or a list that is not a dotted list or a circular list) is something that is listp and also has nil as its last cdr. That is, a list XS is proper if (cdr (last XS)) is nil (and that is how you distinguish it).

Another way to put this is that a proper list has a proper list as its cdr. This is the way the data type (proper) List is defined in typed languages. It is a generic and recursive type definition: The generic part says that the first argument to the non-empty list constructor (often called cons, BTW) can be of any type. The recursive part says that its second argument is an instance of type (proper) List.

Yes, you check whether a given listp is a proper list using (cdr (last XS)) is nil. In order to check whether the cdr of the critter is itself a proper list, you must continue checking its cdr, down to the end - the last cons, to see whether it is nil. You could define a predicate for this as follows, if you wanted to:

(defun true-listp (object)
  "Return non-`nil' if OBJECT is a true list."
  (and (listp object)  (null (cdr (last object)))))

Although a circular list has no end, Emacs (starting with Emacs 24) is smart enough to check last correctly, so this code works even for a circular list (but only for Emacs 24.1 and later; for earlier versions you get an "infinite" recursion until stack overflow).

You can use functions such as length only on proper lists and other sequences. See also function safe-length.

See the Elisp manual, node Cons Cells.

As for notation, (a b) is just syntactic sugar for (a . (b . nil)) -- see the Elisp manual, node Dotted Pair Notation

  • What is the best practice to check for proper list? Checking if (cdr (last XS)) is nil is crumblesome. Isn't there a function like proper-list-p ? – tom May 30 '16 at 10:54
  • @tom: That, or something equivalent, is necessary - you must check the last cons cell. I've added more about this in the answer now. – Drew May 30 '16 at 13:43
  • @Drew I would change the body of the function to (unless (atom x) (not (cdr (last x)))) So you can even call (true-list-p "text") and get nil not an error. – tom May 30 '16 at 14:12
  • @tom: Right; thx. Actually, it should be tested first to ensure it is a cons or nil (i.e., listp). (Also, FWIW, I don't use unless or when for their return value. I use and, or, and if for that.) – Drew May 30 '16 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.