←back to thread

Emacs Lisp Elements

(protesilaos.com)
339 points robenkleene | 5 comments | | HN request time: 0.653s | source
Show context
tikhonj ◴[] No.43667636[source]
I've had a great time using Emacs Lisp over the past 15 years: it's one of the easiest ways to quickly whip up personalized tools for my own use, and, at the same time, my code has been surprisingly resilient and stable over this time.

And this is despite the fact that Emacs Lisp routinely flouts every single software engineering "best practice". The language is dynamically scoped by default! It simply doesn't have namespaces! Static types? Hah! (And I, an inveterate Haskeller, don't even miss them.) You can—and people routinely do—hook directly into all sorts of implementation details from other parts of the codebase.

And yet it just works. And it works remarkably well.

My theory: what matters isn't "best practices", it's have a coherent conceptual design and code that reflects that design. Emacs is designed around a small but expressive set of core concepts that it uses in a consistent manner. Text with properties, buffers, modes, commands, customization variables... Almost everything more complex in Emacs is structured out of these (+ a handful more), and, once you've internalized them, it's surprisingly easy to both learn new higher-level tools and to write your own.

The design of both the user interface and the code directly reflect these concepts which gives us a naturally close connection between the UI and the code (it's almost trivial to jump from an interaction to the code that powers it), makes both UI and code units effortlessly composable and generally makes it easier to understand what's going on and how we can change it.

replies(4): >>43667712 #>>43668618 #>>43671691 #>>43673457 #
nothrabannosir ◴[] No.43667712[source]
> My theory: what matters isn't "best practices", it's have a coherent conceptual design and code that reflects that design.

Just because something has a >0 level of success doesn't mean there are no negatives. "Best practices don't matter because Emacs Lisp doesn't follow them and it just works" isn't a valid argument: it could very well be that Emacs (Lisp) would shine fifteen-fold brighter if it did also follow those practices. It just happens that having those elements you mentioned as positives are enough to keep the train going in spite of the shortcomings.

I use Emacs and program in Emacs Lisp to scratch my itches, and I agree that there are elements that make it work and hey, I'm still here, but I will bet good money that a parallel universe with Emacs Lisp', Now With Namespaces would work even better.

"Working" isn't binary.

replies(3): >>43667940 #>>43668109 #>>43673619 #
tikhonj ◴[] No.43667940[source]
That's certainly true!

But based on my holistic experience using Emacs—more than a binary "it works"—I'm convinced that the conceptual design carries most of the weight.

Would namespaces, lexical scoping, etc be better? Almost definitely. But it would be a marginal improvement, a difference in degree not kind. Other "best practices" like more aggressive information hiding would go against Emacs's design and make it worse as a whole.

Most "best practices" are neither necessary nor sufficient and, in most cases, not even particularly effective, even when they offer strict improvements over the status quo.

replies(1): >>43668569 #
1. TeMPOraL ◴[] No.43668569[source]
> Would namespaces, lexical scoping, etc be better? Almost definitely. But it would be a marginal improvement, a difference in degree not kind.

That's the thing - it probably would not be.

IMO, the key things that allow Emacs to succeed and last, while being somewhat unique in how it "routinely flouts every single software engineering "best practice"", are not merely conceptual, but cultural or even philosophical. The most fundamental of those, IMO, is that Emacs is ultimately owned by users, who are treated as intelligent partners in the relationship. Everything is designed with extensibility in mind. There's no hard boundary between "core developers", "third-party extension developers" and "end-users" - everyone can be any of them, including all three, at any time.

Maximizing extensibility and interoperability, not demanding "users" to coordinate with "developers" and ask for permission, not chasing user growth (it's all Free Software), expecting that everyone is a responsible adult, encouraging users to make Emacs "their own" - all that leads to an approach that goes the opposite to standard software development practice, because the standard practice is built around entirely opposite assumptions.

replies(2): >>43670288 #>>43673316 #
2. Capricorn2481 ◴[] No.43670288[source]
I don't see what any of that has to do with namespaces. You can have all of that and not clobber your functions together. It's just a way to delineate groups of functions, not a organizational mandate.

Same with lexical scoping? Not really following you.

replies(2): >>43671076 #>>43673424 #
3. TeMPOraL ◴[] No.43671076[source]
Lexical scoping is now available as a toggle and recommended for ELisp code, but Emacs was a mature project and ecosystem when this was introduced, so people know when to use it or not use it. Most languages and environments that start with lexical scoping don't even provide dynamic scoping as an option, failing to recognize when it's useful. And to be fair, it's primarily useful in the thing that Emacs does way more than most software - that is, the ability for some code to override behavior of other code up the call stack, regardless of how that other code got to be called, and without involving intermediate callees in this.

(Or, in short, what you're doing each time you're setting environment variables in a shell call. Environment variables are dynamic scoping, just one level up.)

Namespaces are mostly fine if they're only helping against name clashes, and not trying to do access control. Even then, given the Lisp nature and high extensibility of Emacs (two sides of the same coin, actually), normal namespaces you see in other programming languages are a bad fit, as they don't play well with symbols and various patterns around using symbols as designators resolved at runtime. You'd need a namespacing solution like packages in Common Lisp, which is quite different than what people are used to.

In general, none of that would be a show-stopper - it's more that Emacs always leaned on being easy to extend, including extend interactively (via REPL, or more commonly, just selecting and eval-ing blocks of code), and things like namespaces tend to make it more annoying.

4. ◴[] No.43673316[source]
5. funcDropShadow ◴[] No.43673424[source]
Dynamic scoping is a feature in the Emacs ecosystem. It plays a similar role to dependency injection in other languages. It is what allows you to change global state for some function call. Dynamic scoping together with buffer-local, file-local, directory-local variables is similar to scopes in dependency injection libraries --- a powerful organizational tool.