Hacker News new | ask | show | jobs
by klibertp 4259 days ago
Elisp is not Scheme and certainly is not Haskell. You shouldn't - and I'd say it a good general rule - judge Elisp based on what it's not instead of what it is.

Getting back to actual question: Elisp supports "real" lexical scoping since previous major Emacs version, and it has had "lexical-let" and other such forms since forever, but most of the code is still dynamically scoped. In practical terms it's similar to having every variable declared as global - it allows for "out of band" communication (outside of arguments passed/value returned) between routines. This is sometimes handy if you want to change some function behaviour in a way that it didn't think of (ie. it has no argument dedicated for this). As a very contrived example, if you have a routine which beeps furiously every time you invoke it and you find it unbearable (I did) you can instead call it like this:

    (letf
        (((symbol-function 'beep) (lambda ())))
      (beeping-function))
And it won't beep any more. A real life saver sometimes ;)

On the other hand, every out-of-band communication has a set of problems to it: you can forget to check it, or you can accidentally pass something to a called function you didn't want to. In practice this is worked around with using longer, prefixed identifiers and the semantics of `let`. As long as every variable your function uses is let-bound inside it it is essentially safe to call with any kind of environment, as it won't ever look at it. Most functions are like that.

In short, dynamic scoping has it's advantages and drawbacks, and it feels quite well suited to an extension/scripting language of an app. It makes certain patterns easy enough that they don't even need a name ("monkey patching"), and it makes others much harder (like the linked dash-functional library, which would be very hard to write without lexical-scoping: t).

1 comments

Your code fails to suppress beeping behavior on my Mac for certain values of beeping-function. In particular, when I eval the following, I get a beep.

  (require 'cl)

  (setq lexical-binding nil)

  (defun beeping-function ()
      (save-excursion
          (set-buffer "*Messages*")
          (goto-char (point-max))
          (scroll-up)))

  (letf (((symbol-function 'beep) (lambda ())))
      (beeping-function))
I tried to repair it: I ran the code in a fresh Emacs session to which my usual customizations in .emacs had not been applied. I set lexical-binding to t. Since beep is an alias for ding, I replaced beep with ding. I rewrote the letf as a cl-flet.

I think that the fact that scroll-up is built-in is pertinent: there was another incident in the recent past in which the function whose binding I wanted to override ("shadow"?) is called from a built-in function and in which my attempt to use cl-flet to override the binding failed.

If you have Emacs running on a Windows or Linux machine, I would like to know whether the above code causes a beep on that machine. I would like to know because in the past, code that gets a little tricky about bindings that worked on my friend's Windows box failed to work on my Mac (even with an up-to-date Emacs).

ADDED: Actually, let me show you the code that "worked" (i.e., behaved like someone familiar with Scheme or Haskell would expect modulo the trivial "rigamarole" of needing to use funcall) on my friend's Windows box but not on my Mac. The error is "(void-variable a)".

  (funcall
      (funcall
          (lambda (a) (lambda (b) a))
          1)
      2)
As you can easily check with M-x find-function a `scroll-up` function is implemented in C. This is a known problem - I can't find it right now, but I remember I once read a blog post where the author complained about this: in general you're not going to get good results when trying to advise, override or modify C-level functions from Elisp code.

IIRC cl-flet was recently (well, in the current decade I think :)) modified to be lexically scoped, so that won't work (anymore). This is why I had to use `letf` with `(symbol-function 'symbol)` instead of a neater `(flet ((beep ())) ...)`.

Personally I deal with beeping like this:

    (setq ring-bell-function (lambda ()))
but this disables any beeping, which may not be what you want. In short, things you `setq` or more generally `setf` tend to be visible from C functions (no guarantees though).

On the other hand my Emacs (on Linux) refuses to beep in any circumstances even if I run it with `emacs -Q` and do M-: (beep) - I have no idea why, I suspect it's either my sound configuration or has to do with the way I built my emacs from source.

I'd say for this:

    (funcall
      (funcall
          (lambda (a) (lambda (b) a))
          1)
      2)
raising a (void-variable a) error is correct behaviour under dynamic scoping. On my system (Emacs 25.0.50.2 x86_64-unknown-linux-gnu) it fails with void-variable under normal circumstances, but returns 1 when used with lexical scoping enabled. If your Emacs behaves differently (and remember to always check with emacs -Q just to be sure it's not something in your config) that's probably a bug.
>If your Emacs behaves differently . . . that's probably a bug.

Yes, it behaves differently (with lexical-binding set to t and with the -Q flag).