Yeah, I came to the same conclusion about closures when I wrote Ur-Scheme. In mainstream modern languages, you have the remarkable situation that, when you are writing the code, you know which variables you intend your closure to capture, but you don't note that explicitly in the code. Then the compiler needs some extra complexity to calculate the set of free variables in the body of the lambda — and which binding contours they are being captured from, although that's usually pretty trivial. And then the guy who is changing the code next year also needs to reverse-engineer that same set of variables and binding contours in order to modify it successfully. So it seems sort of perverse to leave that information out of the code!
Since then, though, I've come to doubt my conclusion, for two reasons:
1. The same thing is true of, for example, static types; but using type inference or dynamic typing often makes your code easier rather than harder to read. (Some wag said something to the effect that dynamic typing is what you do when it's simultaneously so trivial to see that your program has no type errors that you can do it in your head and so difficult that you don't want to spend the time to do the proof.)
2. There isn't a really compelling difference to me between the closure in
^(x
capturing x and the closure in
fetch(json).then(r => widget.displayJson(r.json()))
capturing widget, or the block in
(let ((overlay (make-overlay beg end)))
(overlay-put overlay 'face (or face 'highlight)))
capturing face.
That is, inner blocks of control structures implicitly capture variables from their outer scope all the time, and this is mostly not a problem; and closures are a useful technique to make it possible to extend the set of control structures. (As it happens, let in Emacs Lisp is implemented as a built-in special form, but in Scheme it's normally implemented as a macro that puts the inner block into a closure.)
Maybe part of the reason is the lifetime: the example with fetch() is in fact socking away a reference to widget until after the HTTP response comes back, so aliasing bugs and space leaks are possible, while the other two examples aren't. This might be a reason implicit closures are so much more popular in functional programming languages: aliasing bugs are not a problem for immutable data, and nobody expects to be able to predict how much memory their functional program will need anyway.