How to live with Emacs Lisp dynamic scoping?

2019-01-30 06:16发布

I've learned Clojure previously and really like the language. I also love Emacs and have hacked some simple stuff with Emacs Lisp. There is one thing which prevents me mentally from doing anything more substantial with Elisp though. It's the concept of dynamic scoping. I'm just scared of it since it's so alien to me and smells like semi-global variables.

So with variable declarations I don't know which things are safe to do and which are dangerous. From what I've understood, variables set with setq fall under dynamic scoping (is that right?) What about let variables? Somewhere I've read that let allows you to do plain lexical scoping, but somewhere else I read that let vars also are dynamically scoped.

I quess my biggest worry is that my code (using setq or let) accidentally breaks some variables from platform or third-party code that I call or that after such call my local variables are messed up accidentally. How can I avoid this?

Are there a few simple rules of thumb that I can just follow and know exactly what happens with the scope without being bitten in some weird, hard-to-debug way?

10条回答
We Are One
2楼-- · 2019-01-30 06:56

Everything that has been written here is worthwhile. I would add this: get to know Common Lisp -- if nothing else, read about it. CLTL2 presents lexical and dynamic binding well, as do other books. And Common Lisp integrates them well in a single language.

If you "get it" after some exposure to Common Lisp then things will be clearer for you for Emacs Lisp. Emacs 24 uses lexical scoping to a greater extent by default than older versions, but Common Lisp's approach will still be clearer and cleaner (IMHO). Finally, it is definitely the case that dynamic scope is important for Emacs Lisp, for the reasons that RMS and others have emphasized.

So my suggestion is to get to know how Common Lisp deals with this. Try to forget about Scheme, if that is your main mental model of Lisp -- it will limit you more than help you in understanding scoping, funargs, etc. in Emacs Lisp. Emacs Lisp, like Common Lisp, is "dirty and low-down"; it is not Scheme.

查看更多
小情绪 Triste *
3楼-- · 2019-01-30 06:57

The other answers are good at explaining the technical details on how to work with dynamic scoping, so here's my non-technical advice:

Just do it

I've been tinkering with Emacs lisp for 15+ years and don't know that I've ever been bitten by any problems due to the differences between lexical/dynamic scope.

Personally, I've not found the need for closures (I love 'em, just don't need them for Emacs). And, I generally try to avoid global variables in general (whether the scoping was lexical or dynamic).

So I suggest jumping in and writing customizations that suit your needs/desires, chances are you won't have any problems.

查看更多
何必那么认真
4楼-- · 2019-01-30 06:58

As Peter Ajtai pointed out:

Since emacs-24.1 you can enable lexical scoping on a per file basis by putting

;; -*- lexical-binding: t -*-

on top of your elisp file.

查看更多
干净又极端
5楼-- · 2019-01-30 07:01

It isn't that bad.

The main problems can appear with 'free variables' in functions.

(defun foo (a)
  (* a b))

In above function a is a local variable. b is a free variable. In a system with dynamic binding like Emacs Lisp, b will be looked up at runtime. There are now three cases:

  1. b is not defined -> error
  2. b is a local variable bound by some function call in the current dynamic scope -> take that value
  3. b is a global variable -> take that value

The problems can then be:

  • a bound value (global or local) is shadowed by a function call, possibly unwanted
  • an undefined variable is NOT shadowed -> error on access
  • a global variable is NOT shadowed -> picks up the global value, which might be unwanted

In a Lisp with a compiler, compiling the above function might generate a warning that there is a free variable. Typically Common Lisp compilers will do that. An interpreter won't provide that warning, one just will see the effect at runtime.

Advice:

  • make sure that you don't use free variables accidentally
  • make sure that global variables have a special name, so that they are easy to spot in source code, usually *foo-var*

Don't write

(defun foo (a b)
   ...
   (setq c (* a b))  ; where c is a free variable
   ...)

Write:

(defun foo (a b)
   ...
   (let ((c (* a b)))
     ...)
   ...)

Bind all variables you want to use and you want to make sure that they are not bound somewhere else.

That's basically it.

Since GNU Emacs version 24 lexical binding is supported in its Emacs Lisp. See: Lexical Binding, GNU Emacs Lisp Reference Manual.

查看更多
登录 后发表回答