4

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."
Jisang Yoo
  • 3,670
  • 20
  • 31

3 Answers3

5

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.

  • The higher-order function mapcar has always been heavily used (and to a lesser extent, mapc and mapconcat). The CL package adds many very useful higher-order functions: mapcan, mapcon, maplist, some, every, notany, notevery, reduce, remove-if, remove-if-not, etc. It would be very limiting to refuse ever to use functional programming. – to_the_crux Nov 02 '13 at 15:11
  • @to_the_crux Oh dear, please read the question, and put my answer into its context. I do *not* refuse functional programming. I simply state the fact that a lot Emacs Lisp code prefers explicit iteration over higher order functions. Of course, Emacs has such functions, as you have observed, but these were (are still?) not frequently used with lambdas, and even less frequently with closures, precisely because Emacs didn't have closures for a long time. And to this day, Emacs Lisp is not a functional language. At best, it is an imperative language embracing some FP techniques. –  Nov 02 '13 at 15:39
4

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).

Stefan
  • 27,908
  • 4
  • 53
  • 82
3

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).

Drew
  • 29,895
  • 7
  • 74
  • 104
  • Thanks to you and @Stefan for your answers. I've been wondering how to avoid name clashes in dynamically scoped elisp, and there seems to be very little information on that topic in the official documentation. – to_the_crux Nov 02 '13 at 15:17
  • 1
    It is not specific to Elisp. You can find lots of info about it by googling wrt Lisp in general. The Common Lisp official doc describes it pretty well, also. In the old days, it was referred to as a "funarg" problem (in fact, there are both downward and upward funarg problems), so "funarg" can also be a useful search term here. – Drew Nov 02 '13 at 16:05