What happens when I use a plain setf statement in LISP?
Asked Answered
S

3

6

I know that when you want to make a dynamic/global binding in Lisp, you use either defparameter or defvar. I also know that you can make lexical bindings, well, almost everywhere, using defun argument lists or let statements.

What I'm wondering is what exactly it is I make when I make a statement like this where x was not declared or used anywhere else in the code:

(setf x 10 )

This appears to work fine, and x doesn't seem to behave like a lexical variable. Is it actually a dynamic global, the same as if I'd used defparameter or defvar, or is it something else entirely?

Skell answered 26/12, 2012 at 9:46 Comment(0)
S
6

What it actually does is unspecified in the ANSI Common Lisp standard.

Generally I prefer any CL implementation to just set the dynamically bound value or global value. It should not to do anything else. CMUCL by default seemed to think that it was a good idea to declare the symbol special then. But that was a bad idea, since there was no obvious way to get rid of a global special declaration.

So, typically I would expect something like this (here, LispWorks):

CL-USER 66 > (defun foo () (setf x44 10))
FOO

The global variable is still unbound:

CL-USER 67 > x44

Error: The variable X44 is unbound.
  1 (continue) Try evaluating X44 again.
  2 Specify a value to use this time instead of evaluating X44.
  3 Specify a value to set X44 to.
  4 (abort) Return to level 0.
  5 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 68 : 1 > :top

Let's call the function:

CL-USER 69 > (foo)
10

Now it has a global value:

CL-USER 70 > x44
10

But the variable is not declared to be special (as it would be by DEFVAR or DEFPARAMETER). Here a lexical binding is established.

CL-USER 71 > (let ((x44 20)) (foo) x44)
20

When we declare the local variable to be special, then our function changes the binding:

CL-USER 72 > (let ((x44 20)) (declare (special x44)) (foo) x44)
10
Semiyearly answered 26/12, 2012 at 13:6 Comment(1)
Thanks! Your answer was really helpful. Using your answer coupled with some testing in the REPL finally helped me fully understand the behaviour that would occur with my original statement. And now I also know the difference between a special and non-special variable.Skell
M
2

Very shortly, you can think of setq, which is the expansion of setf you have observed, as doing only one half of what defvar or defparameter do:

Consider that defparameter does this:

(declaim (special x))
(setq x 10)

I.e. it both provides some meta-data (the data about what sort of thing x is) to the compiler (in this case it tells it that it is a "special" variable) and assigns value.

In particular, defvar will not behave like this, if it is a top-level form. The standard behaviour is to initialize the value-cell of the symbol only once, so its code will be even more complex, something that you can think of as:

(unless (boundp x)    ; This is not entirely correct, because if the symbol
                      ; is otherwise known to the environment, but is unbound
                      ; defvar will not re-bind it, but I can't think of a way
                      ; to mimic that behavior
  (declaim (special x))
  (setq x 10))

The meta-data provided to compiler may or may not have any effect on how the code will behave. In general, the meta-data is supposed to aid the compiler to make better judgement of the intent behind your code, and thus may result in optimizations. But it can also be useful for documentation or debugging.

You can read about special and declare in Hyperspec.

Manhood answered 26/12, 2012 at 10:33 Comment(0)
S
0

As far as i know you should avoid using setf(which expands to setq) with undeclared variables. The behavior can differ between implementations and even if your code in REPL works good, the compiled program can be bugged in unexpected place. You can see warning in clisp if you do something like this:

>(defun internal-setf () (setf some-var 10))
>(compile 'internal-setf)
WARNING: in INTERNAL-SETF : SOME-VAR is neither declared nor bound,
     it will be treated as if it were declared SPECIAL.
Segregation answered 26/12, 2012 at 10:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.