Harry
Harry

Reputation: 3927

Common Lisp: Any way to avoid defvar or defparameter?

I'm using SBCL 2.0.1.debian and Paul Graham's ANSI Common Lisp to learn Lisp.

Right in Chapter 2 though, I'm realizing that I cannot use setf like the author can! A little googling and I learn that I must use defvar or defparameter to 'introduce' my globals before I can set them with setq!

Is there any way to avoid having to introduce globals via the defvar or defparameter, either from inside SBCL or from outside via switches? Do other Lisp's too mandate this?

I understand their value-add in large codebases but right now I'm just learning by writing smallish programs, and so am finding them cumbersome. I'm used to using globals in other languages, so don't necessarily mind global- / local-variable bugs.

Upvotes: 1

Views: 692

Answers (1)

user5920214
user5920214

Reputation:

If you are writing programs, in the sense of things which have some persistent existence in files, then use the def* forms. Top-level setf / setq of an undefined variable has undefined semantics in CL and, even worse, has differing semantics across implementations. Typing defvar, defparameter or defconstant is not much harder than typing setf or setq and means your programs will have defined semantics, which is usually considered a good thing. So no, for programs there is no way to avoid using the def* forms, or some equivalent thereof.

If you are simply typing things at a REPL / listener to play with things, then I think just using setf at top-level is fine (no-one uses environments where things typed at the REPL are really persistent any more I think).

You say you are used to using globals in other languages. Depending on what those other languages are this quite probably means you're not used to CL's semantics for bindings defined with def* forms, which are not only global, but globally special, or globally dynamic. I don't know which other languages even have CL's special / lexical distinction, but I suspect that not that many do. For instance consider this Python (3) program:

x = 1

def foo():
    x = 0
    print(x)
    def bar():
        nonlocal x
        x += 1
        return x
    return bar

y = foo()

print(y())
print(y())
print(x)

If you run this it will print

0
1
2
1

So consider the 'equivalent' CL program (note: never write a program with variables named like this):

(defvar x 1)

(defun foo ()
  (let ((x 0))
    (print x)
    (lambda ()
      (incf x))))

(let ((y (foo)))
  (print (funcall y))
  (print (funcall y))
  (print x)
  (values))

If you run this it will print

0
2
3
3

Which I think you can agree is very different from what the Python program printed.

That's because top-level variables in CL are special, or dynamic variables: in the above CL program x is dynamically scoped because x has been declared globally special by the defvar, and this means that the calls to the function returned by foo are altering the global value of x.

Python doesn't have dynamic bindings at all (but you can write Python modules which will simulate them as functions which access an explicit binding stack, with a bit of deviousness). Something like this is also how other languages expose dynamic bindings. For instance this is how Racket does it:

(define d (make-parameter 1))

(define (foo)
  (parameterize ([d 0])
    (display (d))
    (λ ()
      (d (+ (d) 1))
      (d))))

(let ([y (foo)])
  (display (y))
  (display (y))
  (display (d)))

will print

0
2
3
3

While this program

(define x 1)

(define (foo)
  (let ([x 0])
    (displayln x)
    (λ ()
      (set! x (+ x 1))
      x)))

(let ([y (foo)])
  (displayln (y))
  (displayln (y))
  (displayln x))

will print

0
1
2
1

as the Python one does.

If it wasn't for CL's compatibility requirements this is perhaps how CL should have done this too (that's obviously a personal opinion, and I'm happy with how CL does do it).

CL doesn't have top-level (global) lexical bindings at all (but you can write a CL program which will simulate them in a pretty convincing way using symbol macros). If you want such things I suspect there are already some on the internet or I could get on and tidy up my implementation. As an example of a program which uses such a thing:

(defglex x 1)

(defun foo ()
  (let ((x 0))
    (print x)
    (lambda ()
      (incf x))))

(let ((y (foo)))
  (print (funcall y))
  (print (funcall y))
  (print x))

will print

0
1
2
1

CL has both lexical and dynamic (special) nonglobal bindings.

As a note: the fact that things defined with the def* forms are globally special / dynamic, which means that all bindings of them are dynamic, is why you should always distinguish the names of such variables, using the * convention: (defvar *my-var* ...) and never (defvar my-var ...). ((defconstant my-constant ...) is OK however.)

Upvotes: 3

Related Questions