1

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.

lurker
  • 56,987
  • 9
  • 69
  • 103

3 Answers3

3

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)))
uselpa
  • 18,732
  • 2
  • 34
  • 52
  • Oh geez I knew it was something stupid. Thanks. BTW, could you explain your alternative? I thought the proper syntax of passing a `lambda` to `mapcar` necessitated `#'(lambda...`. I'm still learning. – lurker Jan 03 '14 at 20:16
  • @Vatine yes, thanks. Again it was a brain fart on my part. I see that clearly now. – lurker Jan 03 '14 at 20:21
  • Thanks again uselpa, I understand your use of `prog1` now that I've examined it a bit more. – lurker Jan 03 '14 at 20:38
  • 1
    I think an even simpler solution, though, would be to use `(+ e (incf c))` and just start `c` at `-1` instead of `0`. – Joshua Taylor Jan 03 '14 at 20:58
3

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)
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • Thanks for elaborating, Joshua, that's very helpful. – lurker Jan 03 '14 at 22:57
  • Although seeing as how `incf` and `decf` bot take an optional increment value (defaulting to 1), I would have preferred having that option in `post-incf` as well. – Vatine Jan 04 '14 at 08:47
1

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).

Vatine
  • 20,782
  • 4
  • 54
  • 70
  • Yes, thanks (+1). A big "duh" on my part. That's very clear. I feel like a dork now before all the SO LISP pros... :p – lurker Jan 03 '14 at 20:22