The term "special variable" is CommonLisp
jargon which means "dynamically scoped variable". Which in turn is Lisp jargon that means a variable whose binding has IndefiniteScope
(and is hence a DynamicBinding
means that a special variable can acquire a value on entry into a lexical scope, and when that lexical scope terminates, the previous value of that variable is restored. There is no permanent association of that binding with an instance of the lexical scope, as in the case of a lexical variable. This saving and restoring is carried out implicitly by ordinary binding constructs like LET, which normally work with lexical variables.
means that the binding between the symbol and value is not lexically established, but rather established in a global environment (but see notes about multithreading below). This means that if, for instance, one function establishes a value (via DynamicBinding
) for a special variable, and then calls another function, that other function can evaluate that special variable to obtain the value established by the former function, and can also assign to that variable to communicate a new value back to that function. The two are not nested in any way, and can be in separately compiled parts of the program. (For "function", substitute "expression", actually. DynamicBinding
s are associated with binding construct expressions, not function bodies. In one body, you can have nested bindings for the same variable).
For example the standard special variable *print-base* controls the base for printing integers. We can temporarily override it to make some code output in hex instead.
(defun print-some-numbers ()
(print-some-numbers) ;; output is 10 20 30 on separate lines
(let ((*print-base* 16)) (print-some-numbers)) ;; output is A 14 1E
(print-some-numbers) ;; output is 10 20 30 again
In Lisps that support threading (which includes every commercial, proprietary implementation), the dynamic bindings of special variables are thread-specific. Thus special variables provide a convenient abstraction for thread-specific storage. If one thread overrides *print-base* to print something in hex, that won't affect I/O concurrently happening in another thread. However, if two threads use the same SpecialVariable
and don't establish their own DynamicBinding
for it, then they are sharing the same global binding! This sharing is fine as long as it is read-only; for example, it can be used to establish a default value for some parameter that can be overriden locally to change a thread's behavior.
One useful way to think of dynamic variables is to pretend that they are invisible parameters which go into every function, and are passed down again, without any requirement of cooperation from the function writer. If you have a lot of state information that needs to be passed around, and even travel through external functions that call you back, special variables can significantly reduce the complexity of your code.
They can parameterize the behavior of a module in a disciplined way; they are "tamed" global variables that support concurrency and re-entrance.
Here is a StupidXmlProject
called XVCL which re-invents macros and special variables. The main ideas are: XML is subject to substitution of variant parts into invariant templates (i.e. macro processing) and that substitution values can be passed through intermediate frames without the involvement of their parameter list (special variables). http://fxvcl.sourceforge.net/home.htm
See also ScopeAndClosures