1
votes

I am running clisp in Linux, and playing with the exercises in the book, ANSI Common Lisp. One of them says to use mapcar to create a function which takes a list of integers and returns a list in which each element is the original element plus its index in the list. So if I execute (foo '(0 0 0)) it would produce (0 1 2), etc.

What I tried is this:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x) (+ x c) (incf c)) lst)))

What I get when I run (foo '(0 0 0)) is (1 2 3). Just as an experiment, I swapped the order of the increment around and defined it this way:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x) (incf c) (+ x c)) lst)))

and I got exactly the same results (in this case, I would expect (1 2 3), but not in the former case). I also tried wrapping the sequence like this (progn (+ x c) (incf c)) and got the same result. To make the function work, I need to do (let ((c -1))). Why does the order of (incf c) and (+ x c) not seem to matter in this context? It would appear that (incf c) is always done first regardless, but I am not sure why. I know I'm missing something fundamental here, so this should be a quick and easy answer from a LISP pro to explain to me why this works this way. :)

Thanks.

3
Nevermind, I'm a dummy. :-/ Kudos to those who answered and helped me see it. - lurker

3 Answers

3
votes

In your first example, the result of the lambda function is the result of the last form, (incf c). (+ x c) is ignored.

In your second example, you first increment c, so 1 is added to the first number, 2 to the second, etc.

For your first example, you could also use prog1 to return the value of the first form, not the last:

(defun foo (lst)
  (let ((c 0))
    (mapcar
     (lambda (e) (prog1 (+ e c) (incf c)))
     lst)))
3
votes

Note: this is aimed at exploring some of the ways to make this happen. You noted in your question some of these solutions (e.g., starting at -1), and I don't mean by including them here to suggest that you hadn't considered them. This answer is more about considering the role of a pre- and post-increment operators in value-oriented languages.

Note that (incf c) not only performs a side effect (where order would matter), but also returns the new value of c. That is, incf is a pre-increment operator. That means that it's absolutely acceptable to write (+ x (incf c)), as in:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (incf c)))
            lst)))

(foo '(0 0 0))
;=> (1 2 3)

Now, those results aren't terrible, but they don't add the indices of the elements to the elements, because indices start at zero, not one. You could do a simple change and add the subtraction:

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (1- (incf c)))) ; or (+ x -1 (incf c)), etc.
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

Even better than this, in my opinion, would be to use the first form, and just start c at -1. It's what I'd probably do, anyhow, and you noted in your question that this is an option:

(defun foo (lst)
  (let ((c -1))
    (mapcar #'(lambda (x)
                (+ x (incf c)))
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

We might ask, though, whether there's a way to approximate a post-increment operator so that we didn't have to see (in our code) the subtraction? That is, we might view the above as what we have in some languages:

result.add( x + ++c )

and ask why we can't just do

result.add( x + c++ ) 

instead? This is simple enough:

(defmacro post-incf (place)
  `(1- (incf ,place)))

(defun foo (lst)
  (let ((c 0))
    (mapcar #'(lambda (x)
                (+ x (post-incf c)))
            lst)))

(foo '(0 0 0))
;=> (0 1 2)

Actually, there's no flexibility in C-like language's pre- and post-increment operators, but incf and decf in Common Lisp afford a little bit more through the use of optional arguments, wherein a second, optional argument, can be used to specify by how much the value should change. It's not too hard to adjust post-incf to handle such a second argument. A first attempt might be:

(defmacro post-incf (place &optional (delta-form 1))
  `(- (incf ,place ,delta-form) ,delta-form))

That will work just fine for constant values such as 1, but it will evaluate the delta-form multiple times, and we don't want that (since it could have side effects, or be expensive to compute). We just need to be a little more careful to evaluate it only once, and thus:

(defmacro post-incf (place &optional (delta-form 1))
  (let ((delta (gensym (symbol-name '#:delta-))))
    `(let ((,delta ,delta-form))
       (- (incf ,place ,delta) ,delta))))

(let ((a 0))
  (list a                ; => 0
        (incf a 3)       ; => 3
        (post-incf a 2)  ; => 3 (but x is now 5)
        a))              ; => 5
;=> (0 3 3 5)
1
votes

In your first lambda, (lambda (x) (+ x c) (incf c)), you are performing one side-effect-free addition and throwing away its value. Then you increment c and return, as the value from the lambda, its new value.

Try calling (foo '(10 10 10)) and see what the return value is, the two versions will return (1 2 3) and (11 12 13).