1

For example, I have function stack-push which pushes element onto the stack.

(defun stack-push (stack element)
    (if (not (listp element))
        (setf stack (cons element stack))
        (dolist (current-el (reverse element))
            (setf stack (cons current-el stack)))))

But when I invoke it like (stack-push *some-stack* '(a b c d e)), it doesn't affect on *some-stack*. Could you explain why?

Олег
  • 67
  • 1
  • 5
  • 1
    Because Lisp calls functions with the values, not the variables themselves. All your SETF does is changing the local variable `stack`. – Rainer Joswig May 06 '18 at 21:07
  • Possible duplicate of [Common lisp push from function](https://stackoverflow.com/questions/16396224/common-lisp-push-from-function) – Kaz May 07 '18 at 13:08
  • 1
    Also: [Push doesn't modify the list being a function argument](https://stackoverflow.com/questions/34793011/push-doesnt-modify-the-list-being-a-function-argument) – Kaz May 07 '18 at 13:10

2 Answers2

3

setf with a symbol as place like (setf stack (cons element stack)) expands to (setq stack (cons element stack)). This usually happens when you create your function. Your function becomes this:

(defun stack-push (stack element)
  (if (not (listp element)
      (setq stack (cons element stack))
      (dolist (current-el (reverse element))
        (setq stack (cons current-el stack)))))

Note that I have only expanded setf here. Both defun and dolist becomes quite horrendous expansions that makes the code more unreadable. The system expands the forms fully so the code that runs has no macros.

setq updates a binding so when it updates stack that is what it updates, not *some-stack*. Here is exactly what is happening if you do (stack-push *some-stack* "a"):

  1. You pass the function *some-stack* and "a". It evaluates its arguments.
  2. *some-stack* evaluates to address A that has a cons cell eg. ("b").
  3. "a" is a literal residing at address B
  4. The parameters gets bound to new bindings. stack points to A and element points to B.
  5. Since element points to B (not (listp element)) ; ==> t and the code follows the consequent.
  6. in (setf stack (cons element stack)) was before runtime changed to (setq stack (cons element stack)).
  7. (cons element stack) calls cons with B and A as arguments. returns a new cell with address C ("a" "b")
  8. setq updates so that binding stack points to C.
  9. stack-push returns the last evaluated value, which is the result of the setq which is the second argument C.

In the function there is no mention of *some-stack*. The binding is never references or updated. Only stack is updates with what new value to point to.

As a consequence of this being a function you can call your function with literals, which normally would not work if the argument was a variable meant to be updated.

(stack-push '("b") "a") ; ==> ("a" "b")

There is a form called push. It is not a function. You can see what it does:

(macroexpand '(push "a" *some-stack*)) 
; ==> (setq *some-stack* (cons "a" *some-stack*))

Thus for push to work you need that second argument is something setf-able. Trying it with literals expands to unrunnable code. For types you can make your own setf expander such that setf and push works.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
2

As a note to Sylwester's answer, here is a version of your stack-push which at first sight looks correct but which in fact has an ugly problem (thanks to jkiiski for pointing this out!), followed by a simpler version which still has the problem, and finally a variant of the simpler version which does not.

Here is the initial version. This agrees with your signature (it takes either a single argument which cannot be a list, or a list of arguments, and decides what to do based on what it sees).

(defmacro stack-push (stack element/s)
  ;; buggy, see below!
  (let ((en (make-symbol "ELEMENT/S")))
    `(let ((,en ,element/s))
       (typecase ,en
         (list
          (setf ,stack (append (reverse ,en)
                               ,stack)))
         (t
          (setf ,stack (cons ,en ,stack)))))))

However I would be more tempted to write it with an &rest argument, as follows. This version is simpler because it always does one thing. It is still buggy though.

(defmacro stack-push* (stack &rest elements)
  ;; still buggy
  `(setf ,stack (append (reverse (list ,@elements)) ,stack)))

This version could be used as

(let ((a '()))
  (stack-push* a 1 2 3)
  (assert (equal a '(3 2 1))))

for instance. And it seems like it works.

Multiple evaluation

But it doesn't work, because it can multiply-evaluate things which should not be multiply-evaluated. The easiest way (I found) to see this is to look at what the macro expansion is.

I have a little utility function to do this, called macropp: this just calls macroexpand-1 as many times as you ask for, pretty printing the result. To see the problem you need to expand twice: first to expand stack-push* and then to see what happens to the resulting seetf. The second expansion is implementation-dependent, but you can see the problem. This sample is from Clozure CL which has a particularly simple expansion:

? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (setf (foo (a)) (append (reverse (list 1)) (foo (a))))
-> (let ((#:g86139 (a)))
     (funcall #'(setf foo) (append (reverse (list 1)) (foo (a))) #:g86139))

And you can see the problem: setf doesn't know anything about foo so it's just calling #'(setf foo). It carefully makes sure that the subforms are evaluated in the right order, but it just evaluates the second subform in the obvious way, with the result that (a) gets evaluated twice, which is wrong: if it has side-effects then they will happen twice.

So the fix for this is to use define-modify-macro whose job is to solve this problem. To do this, you define a function which makes the stack, and then use define-modify-macro to make the macro:

(defun stackify (s &rest elements)
  (append (reverse elements) s))

(define-modify-macro stack-push* (s &rest elements)
  stackify)

And now

? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
     (funcall #'(setf foo) #:g86169 #:g86170))
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
     (funcall #'(setf foo) #:g86169 #:g86170))

And you can see that now (a) is only evaluated once (and also that you only now need a single level of macroexpansion).

Thanks again to jkiiski for pointing out the bugs.


macropp

For completeness, here is the function I use to pretty-print macro expansions. This is just a hack.

(defun macropp (form &optional (n 1))
  (let ((*print-pretty* t))
    (loop repeat n
          for first = t then nil
          for current = (macroexpand-1 form) then (macroexpand-1 current)
          when first do (format t "~&-- ~S~%" form)
          do (format t "~&-> ~S~%" current)))
  (values))
Community
  • 1
  • 1
  • 1
    You're still evaluating the place (`stack`) multiple times. It might have non-pure subforms, which would cause problems. It would be easier to write a regular function that returns the new value, and use `DEFINE-MODIFY-MACRO` to make a modify macro for it. – jkiiski May 06 '18 at 19:24
  • @jkiiski I'm not as far as I can see: there are at most two occurrences of the stack (because `typecase` only evaluates one of the two cases) and only one of them is evaluated normally, the other being the first subform in `setf`. –  May 07 '18 at 09:45
  • 1
    For example, `(setf (foo (random 10)) (+ 10 (foo (random 10))))` evaluates `(random 10)` twice. – jkiiski May 07 '18 at 09:56
  • @jkiiski: oh, yes, I feel stupid now. I've modified the answer fairly extensively (with credit). Thanks! –  May 07 '18 at 11:05