Search code examples
lispcommon-lispclisp

Can't change list


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?


Solution

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