In Common Lisp, how to test if variable is special?
Asked Answered
H

4

6

I thought I would be able to find this through Google, SO, or the books I'm reading, but it is proving elusive.

In the implementation I'm learning with, I can do the following at the top-level:

(defvar *foo* 4) (set 'bar 3)

If I then call (describe '*foo*) and (describe 'bar), I get a description saying that *foo* is special and bar is non-special (among other details).

Is there a function that takes a symbol variable as an argument and returns true or false if it is special? If so, is describe probably implemented in part by calling it?

Context: I'm learning Common Lisp, but at work I have a system with a dialect of Lisp similar to Common Lisp, but the describe function is unimplemented. There's sort of an XY thing going on here, but I'm also trying to grok Lisp and CL.

Horaciohorae answered 4/2, 2015 at 17:59 Comment(0)
B
5

A non-special variable's environment will be captured when you create a closure over it:

(let ((x 1))
  (let ((f (lambda () x)))
    (let ((x 2))
      (eql 2 (funcall f)))))
;;=> NIL

A special variable's lexical environment will not:

(defvar *x*) ; *x* is special

(let ((*x* 1))
  (let ((f (lambda () *x*)))
    (let ((*x* 2))
      (eql 2 (funcall f)))))
;;=> T

Using this approach, you could easily define a macro that will expand to code like the previous that will let you determine whether a symbol is globally proclaimed special:

(defmacro specialp (symbol)
  (let ((f (gensym "FUNC-")))
    `(let ((,symbol 1))
       (let ((,f (lambda () ,symbol)))
         (let ((,symbol 2))
           (eql 2 (funcall ,f)))))))

(specialp x) ;=> NIL
(specialp *x*) ;=> T

Note that this isn't a function, it's a macro. That means that the macro function for specialp is getting called with the symbols X and *X*. This is important, because we have to construct code that uses these symbols. You can't do this with a function, because there'd be no (portable) way to take a symbol and create a lexical environment that has a lexical variable with that name and a lambda function that refers to it.

This also has some risks if you try to use it with certain symbols. For instance, in SBCL, if you try to bind, e.g., *standard-output* to something that isn't a stream or a stream designator, you'll get an error:

CL-USER> (specialp *standard-output*)
; in: SPECIALP *STANDARD-OUTPUT*
;     (LET ((*STANDARD-OUTPUT* 1))
;       (LET ((#:FUNC-1038 (LAMBDA # *STANDARD-OUTPUT*)))
;         (LET ((*STANDARD-OUTPUT* 2))
;           (EQL 2 (FUNCALL #:FUNC-1038)))))
; 
; caught WARNING:
;   Constant 1 conflicts with its asserted type STREAM.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; 
; compilation unit finished
;   caught 1 WARNING condition
Biddle answered 4/2, 2015 at 19:19 Comment(3)
Is the gensym in there so that specialp doesn't blow up if you pass it f?Horaciohorae
@Horaciohorae Yes. I suppose that you could also use a locally defined function (e.g., with flet), and avoid the issue altogether, but it still is usually better practice to avoid binding names that might be bound elsewhere.Biddle
This was very clever, but I still think one should know this at code time :-)Marmoreal
F
10

Many Common Lisp implementations provide the function variable-information in some system dependent package.

Here in SBCL:

* (require :sb-cltl2)
NIL

* (sb-cltl2:variable-information '*standard-output*)
:SPECIAL
NIL
((TYPE . STREAM))

This function was proposed as part of some other functionality to be included into ANSI CL, but didn't make it into the standard. Still many implementations have it. For documentation see: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node102.html

Fabulist answered 4/2, 2015 at 19:12 Comment(0)
B
5

A non-special variable's environment will be captured when you create a closure over it:

(let ((x 1))
  (let ((f (lambda () x)))
    (let ((x 2))
      (eql 2 (funcall f)))))
;;=> NIL

A special variable's lexical environment will not:

(defvar *x*) ; *x* is special

(let ((*x* 1))
  (let ((f (lambda () *x*)))
    (let ((*x* 2))
      (eql 2 (funcall f)))))
;;=> T

Using this approach, you could easily define a macro that will expand to code like the previous that will let you determine whether a symbol is globally proclaimed special:

(defmacro specialp (symbol)
  (let ((f (gensym "FUNC-")))
    `(let ((,symbol 1))
       (let ((,f (lambda () ,symbol)))
         (let ((,symbol 2))
           (eql 2 (funcall ,f)))))))

(specialp x) ;=> NIL
(specialp *x*) ;=> T

Note that this isn't a function, it's a macro. That means that the macro function for specialp is getting called with the symbols X and *X*. This is important, because we have to construct code that uses these symbols. You can't do this with a function, because there'd be no (portable) way to take a symbol and create a lexical environment that has a lexical variable with that name and a lambda function that refers to it.

This also has some risks if you try to use it with certain symbols. For instance, in SBCL, if you try to bind, e.g., *standard-output* to something that isn't a stream or a stream designator, you'll get an error:

CL-USER> (specialp *standard-output*)
; in: SPECIALP *STANDARD-OUTPUT*
;     (LET ((*STANDARD-OUTPUT* 1))
;       (LET ((#:FUNC-1038 (LAMBDA # *STANDARD-OUTPUT*)))
;         (LET ((*STANDARD-OUTPUT* 2))
;           (EQL 2 (FUNCALL #:FUNC-1038)))))
; 
; caught WARNING:
;   Constant 1 conflicts with its asserted type STREAM.
;   See also:
;     The SBCL Manual, Node "Handling of Types"
; 
; compilation unit finished
;   caught 1 WARNING condition
Biddle answered 4/2, 2015 at 19:19 Comment(3)
Is the gensym in there so that specialp doesn't blow up if you pass it f?Horaciohorae
@Horaciohorae Yes. I suppose that you could also use a locally defined function (e.g., with flet), and avoid the issue altogether, but it still is usually better practice to avoid binding names that might be bound elsewhere.Biddle
This was very clever, but I still think one should know this at code time :-)Marmoreal
M
1

Defining globals with set or setq is not supported. There are 2 common ways to define globals:

(defparameter *par* 20) ; notice the earmuffs in the name!
(defvar *var* 30)       ; notice the earmuffs in the name!

All global variables are special. Lexically scoped variables (not special) are not possible to get described. E.g.

(let ((x 10))
  (describe 'x)) ; ==> X is the symbol X

It describes not the lexical variable but the symbol representation. It really doesn't matter since you probably never need to know in run time since you know this when you're writing if it's a bound lexical variable or global special by conforming to the earmuffs naming convention for global variables.

Marmoreal answered 4/2, 2015 at 18:35 Comment(4)
"Defining globals with set or setq is not supported" Setq doesn't define a global, but using set with a symbol is perfectly well defined. It just sets the value cell of the symbol. it's not required that the symbol be a global variable.Biddle
I changed foo to *foo* so that the intent is more clear. I understand that using earmuffs helps clarify programmer intent, but [I don't think] it matters to the compiler.Horaciohorae
I'm staring at "Lexically scoped variables (not special) are not possible to get described" and the example you provided. Seems like I might get a lot of insight out of this.Horaciohorae
@Horaciohorae Lexical variables don't really exist at runtime; they're not objects. They're not something you could pass to a function. When you evaluate (foo bar), the system calls the function named by foo and calls it with the value of bar. When you evaluate (foo 'bar), the system calls the function named by foo with the value of 'bar, which is the symbol bar. What you will be able to do, though, is determine whether a symbol is globally declared special.Biddle
W
1

I believe the only way to get this information at run time* is by either using an extension to CL, as Rainer noted, or to use eval.

(defun specialp (x)
  (or (boundp x)
      (eval `(let (,x)
               (declare (ignorable ,x))
               (boundp ',x)))))

(Defect warning: If the variable is unbound but declared to be a type incompatible with nil, this could raise an error. Thanks Joshua for pointing it out in his answer.)

* The macro approach determines which symbol it is checking at macro expansion time, and whether that symbol is lexical or special at compile time. That's fine for checking the status of a variable at the repl. If you wanted to e.g. print all of the special variables exported by a package, though, you would find that to use the macro version you would end up having to use eval at the call site:

(loop for s being the external-symbols of :cl-ppcre
      when (eval `(specialp-macro ,s)) do (print s))
Woodcut answered 5/2, 2015 at 9:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.