Emacs Lisp vs. Scheme: scoping and globals
I've been considering an elisp back-end for CPSCM (so that we can program Emacs in R5RS Scheme). I thought the lack of lexical scoping would prove a major stumbling block, but in the end it turns out that Elisp will be somewhat easier to support than Common Lisp. Here are the twists and turns (to evaluate Elisp code, go to the *scratch* buffer, paste the code and type C-x C-e):
- Elisp has dynamic scope by default:
(defun f () y) (let ((y 10)) (f)) ;; 10 ;; lambda arguments are also dynamic (funcall (lambda (y) (f)) 11) ;; 11
- However, with (require 'cl) you get access to the (lexical-let ...) macro, which does exactly what the name says (there is also a lexical-let*)
- Using lexical-let, one can easily define lexical-lambda — here's a simple version (optimized for minimal line lengths, not Lisp-ness)
(defmacro lexical-lambda (args &rest body) (lexical-let* ((r '&rest) (g (lambda (x) (if (eq x r) x (gensym)))) (gvars (mapcar g args)) (bnd (mapcar* #'list args gvars))) `(lambda ,gvars (lexical-let ,(delete-if (lambda (b) (eq (car b) r)) bnd) ,@body))))
OK, so we've played catch up with Common Lisp and managed to work around dynamic scoping; here's the beautiful part:
- Elisp has sane(r) globals (from a Schemer's POV, at least)
To those who haven't bashed their heads against this problem, Common Lisp's "normal" way of declaring globals (defvar / defparameter) makes variables "pervasively special" (i.e. dynamic) — meaning that
(defvar myvar 10) (defun (f) myvar) (defun g () (let ((myvar 1)) (f))) (g) ;; => 1, not 10
This is not such a problem for Lisp, but what with Scheme being a Lisp-1, translation of global functions is problematic:
(define (f x) x) ;; Scheme ;; Lisp translation -- broken (defvar f (lambda (x) x))
There's another standard-compliant way to simulate globals in Lisp (using symbol macros — search comp.lang.lisp for deflex); however this method requires you to define each global before referencing it, which would preclude mutually-recursive global functions:
(deflex f (lambda (x) (funcall g (- x 1)))) ;; broken: g undefined (deflex g (lambda (x) (if (> x 0) (funcall f x) 0)))
There are other, more convoluted ways to implement "non-special" globals that have elicited endless (and inconclusive, as far as I could tell) threads on comp.lang.lisp, e.g. using (locally (declare (special myvar))). Finally, in many Lisp's one can simply use (setq myvar ...) and at most get a warning, but this is not standards-compliant.
As luck would have it, setq globals work in Elisp too, and the manual seems to indicate that this is intended semantics, not accident. So this will save me the pain of working around Common Lisp's "special" variable rules (I've never found a satisfactory solution), which is why I'm happy about Elisp.
Of course there are areas that need work, e.g. an easier "FFI" to access Elisp functions from Scheme (currently, one has to define a CPS-style wrapper in the back-end with the proper mangled name to make a function callable from CPSCM). But I find the prospect of programming Emacs in Scheme a pretty good motivation...