I recently discovered that elisp is dynamically scoped as opposed to lexically scoped. Out of curiosity (as I've never programmed in a dynamically scoped language) hows the experience of working in such language?
Having spent a lot of time with Haskell and Scheme, what irks me the most about working in Emacs Lisp is the fact that it usually involves too much rigamarole or just fails to work for me to use a function that takes a function as an argument or returns a function as a result. E.g., in Haskell, I am fond of using `flip` to reverse two actual arguments just because it makes a line or 3 of code look nicer, and I cannot do that in Emacs Lisp -- or I can, but it involves adding additional lambda forms or calls to funcall or symbol-function (i.e., it involves too much rigamorole).
Since I cannot say exactly how far lexical scoping would go in making Emacs Lisp more like Haskell in this regard, this is only a partial answer to your question, but certainly, the lack of lexical scope has something to do with it.
Aside from the deficiencies I just mentioned around higher-order functions, I was surprised by how little the lack of lexical scope got in my way. (My reading of the computer-science literature had given me the impression it would cause more trouble than it actually does.)
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:
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).
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)".
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.
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.