About me

About me


RSS feed

More useful versions of mapc and mapcar

19th October 2018

The mapping functions such as mapc and mapcar often provide a much more succint and intuitive way of implementing operations than their iterative equivalents. For example, isn't:

(mapcar #'- '(1 2 3 4))

a much neater way of negating every number in a list, than:

(let (result)
  (dolist (i '(1 2 3 4) (reverse result))
    (push (- i) result)))

However, I often wish that if one of the arguments to mapc or mapcar was an atom, rather than list, that item would be repeated for each evaluation of the function. So if we wanted to double every number in a list I'd like to be able to write:

(mapcar #'* '(1 2 3 4) 2)

but this is invalid in Common Lisp and gives an error such as:

Error: 'mapcar' third argument is not a list

A typical application often occurs with my uLisp interpreter for the Arduino [1]; this provides a function pinmode to define whether a pin is an input or output; so:

(pinmode pin nil)

makes pin an input, and:

(pinmode pin t)

makes it an output. What I would like to be able to do is call:

(mapc #'pinmode '(1 2 3 4) t)

to define all the pins 1 to 4 as outputs.

The solution is to write:

(mapc #'(lambda (x) (pinmode x t)) '(1 2 3 4))

which loses some of the elegance of using a mapping function, or:

(mapc #'pinmode '(1 2 3 4) '(t t t t))

which is also a bit ugly.

Fixed that for you

The great thing about Lisp is that if you don't like something you can usually fix it.

One way to fix it is to write a function o that makes a circular list out of its argument:

(defun o (n)
  (let ((l (list n)))
    (setf (cdr l) l)))

For example, in LispWorks [2] evaluating (o 2) returns:

> (o 2)
(2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 ...)

We can now write things such as:

> (mapcar #'* '(1 2 3 4) (o 2))
(2 4 6 8)

This solves the original problem like this:

(mapc #'pinmode '(1 2 3 4) (o t))

Making a new version of mapc

Alternatively we can write a new version of mapc, called mapc*, which automatically repeats an argument if it's an atom rather than a list:

(defun mapc* (fun arg1 arg2)
  (mapc fun 
     (if (listp arg1) arg1 (o arg1))
    (if (listp arg2) arg2 (o arg2))))

Now we can write:

(mapc* #'pinmode '(1 2 3 4) t)

Other solutions

I'll be interested in hearing whether anyone has a more elegant solution to this question

  1. ^ uLisp - Lisp for microcontrollers.
  2. ^ Unfortunately my uLisp interpreter doesn't currently know how to print circular lists, so it gets stuck in a loop.

blog comments powered by Disqus