dealing with a Emacs Lisp dynamic scope pitfall in old days

309 views Asked by At

In the old days, Emacs had no support for lexical scope. I am wondering how people dealt with a particular pitfall of dynamic scope in those days.

Suppose Alice writes a command my-insert-stuff which relies on the fp-repeat function defined in fp.el (which we suppose is a library providing lots of functions for functional programming written by Bob) and suppose fp-repeat is for repeatedly calling a function many times.

Part of contents of init.el from Alice:

(require 'fp)

(defun my-insert-stuff ()
  (interactive)
  ;; inserts "1111111111\n2222222222\n3333333333" to current buffer
  (dolist (i (list "1" "2" "3"))
    (fp-repeat 10
               (lambda ()
                 (insert i)))
    (insert "\n")))

Part of contents of fp.el from Bob:

(defun fp-repeat (n func)
  "Calls FUNC repeatedly, N times."
  (dotimes (i n)
    (funcall func)))

Alice soon finds that her command doesn't work like she expects. That's because Alice's use of i and Bob's use of i collide. In the old days, what could Alice or/and Bob do to prevent this kind of collision from happening?

Maybe Bob could change the docstring to

"Calls FUNC repeatedly, N times.
Warning: Never use i, n, func in FUNC body as nonlocal variables."
3

There are 3 answers

2
AudioBubble On BEST ANSWER

Alice would have taken care to not use non-local variables in lambda bodies, being aware that lambda did not create lexical closures.

In Emacs Lisp, this simple policy is actually sufficient to avoid most issues with dynamical scoping, because in the absence of concurrency, local let-bindings of dynamic variables are mostly equivalent to lexical bindings.

In other words, Emacs Lisp developers of the “old days” (which are not so old given the amount of dynamically scoped Emacs Lisp still around) would not have written a lambda like that. They would not even have wanted to, because Emacs Lisp was not a functional language (and still isn't), thus loops and explicit iteration were often preferred over higher order functions.

With regards to your specific example, Alice of the “old days” would just have written two nested loops.

0
Stefan On

The way Emacs has dealt with the problem is by following a very strict convention: Elisp programmers writing higher-order functions (such as your fp-repeat) were expected to use unusual variable names when a ray of light made them aware that the function could be used by others, and when the ray of light didn't work, they were expected to do their daily prayers (always a good idea in the Church of Emacs).

2
Drew On

In addition to what lunaryorn and Stefan have said:

In the particular example you gave, the funarg passed to fp-repeat does NOT, in fact, need variable i at all.

That is, it does not need to do anything with i AS A VARIABLE. That is, it does not need to use i as a particular SYMBOL whose value is to be determined at a particular time or in a particular context (environment), when & where the function is called.

All that function really needs is the VALUE of i when and where the function is defined. Using a variable in this particular case is overkill --- only its value is needed.

So another way to thread the needle is to substitute the value for the variable in the definition of the function, i.e., in the lambda expression:

 (defun my-insert-stuff ()
   (interactive)
   (dolist (i (list "1" "2" "3"))
     (fp-repeat 10 `(lambda () (insert ',i)))
     (insert "\n")))

This works fine. There is no possibility of variable capture because there is no variable.

The drawback is that there is also no function at compile time: a LIST is constructed, whose car is lambda etc. And that list is then evaluated at runtime, interpreted as a function.

Depending on the concrete use case, this can be a useful way to go. Yes, it means you must distinguish contexts in which you really need to use a variable (what the function does makes use of VARIABLE i, not just a value).