In simple Lisp code it makes not much of a difference. Dynamic binding might be more in the spirit of early Lisp dialects. Lexical binding is more in the spirit of Functional Programming, where we often use higher-order functions in combination with closures.
There are a few situations which speak in favor of lexical binding.
Let's look at dynamic binding and special variables.
free variables get their bindings at runtime
Sylvester has described one problem in his answer: free variables can get their values at runtime via dynamic binding.
CL-USER 27 > (defun foo (x)
(declare (special x y))
(+ x y))
FOO
CL-USER 28 > (let ((y 10)) (declare (special y)) (foo 32))
42
CL-USER 29 > (foo 32)
Above can lead to situations which can only debugged at runtime. From the source it is hard to reconstruct which values y
will have, since the binding can happen at arbitrary places in the calling chain. Richard Stallman liked that feature - he thought that it was a good choice for an extension language of an editor. Common Lisp uses special variables for many things, for example in its I/O system. For example streams don't need to be passed all the time via parameters.
free variables may not get their bindings at runtime
CL-USER 29 > (foo 32)
Error: The variable Y is unbound.
If we don't define top-level defaults for the free special variables, we can't just call the function...
Not Closures
CL-USER 31 > (let ((x 10)) (declare (special x))
(lambda (y) (declare (special y x)) (+ y x)))
#<anonymous interpreted function 40600011F4>
Unfortunately this is not a closure.
Note that the variable *
holds the last result, here the function above.
CL-USER 32 > (let ((x 22)) (declare (special x)) (mapcar * '(1 2 3)))
(23 24 25)
The function uses the dynamic binding.
CL-USER 33 > (let ((x 10)) (declare (special x)) (lambda (y) (declare (special y x)) (+ y x)))
#<anonymous interpreted function 406000C304>
CL-USER 34 > (mapcar * '(1 2 3))
Error: The variable X is unbound.
Again, it is not a closure and we can't just pass it around...
Summary
We really want a language where we can work with closures by default, where the compiler can warn about unbound variables and where most of the bindings are easy to spot in the source code.