Apart from some legacy warts, I find eLisp about a million times more elegant than mainstream languages.
Scheme would be even better, and hopefully one day emacs can transition to that.
Apart from some legacy warts, I find eLisp about a million times more elegant than mainstream languages.
Scheme would be even better, and hopefully one day emacs can transition to that.
Quick comparison to some other languages:
- vimscript: lol
- JavaScript: the lack of dynamic scoping may be tricky for extensibility, and not having macros is a shame (eg save-excursion and friends). But the language is mostly reasonable. One interesting aspect of elisp is that functions are typically just the symbols of their names, so it is easy to inspect and modify lists of hooks. I also worry about the pervasive use of modularity constructs like IIAFs—a great feature of elisp is the ability to poke and advise other code
- C or Rust: I think it is too hard to do something small or hacky in these languages and the constraints of type systems and manual memory management make ad-hoc extensibility hard.
I’ve also seen emacs (module) extensions written in a strongly typed functional language and they sort of work but can be fragile (eg advising a function but making incorrect assumptions about their types), or needing to use functions and closures for common operations like switching buffer or saving and restoring state. (It also amazes me that more languages don’t have unwind-protect and predictable evaluation order as standard, but that’s another topic)
(I know there was one black magic implementation of it for C# on Windows at some point and also similar black magic for Java, but that's as far as I've ever explored it other than in Emacs Lisp. (Black magic in this case refers to splicing in the advice in the compiled bytecode of the program.))
Edit: apparently there's some basic implementation for Perl, too, and it's documentation looks like a neat intro to the idea: https://metacpan.org/pod/Aspect
Monkey patching comes somewhat close. But lacks the communication of intent that is so clear with advice.
I'm not sure what you mean by this. The idea with aspect-oriented programming is that you can create a bundle of functionality that extends existing functionality but exists as its own separate bundle.
In other words, you specifically do not have to put the advice at the same place as the adviced. That would just be regular procedural/functional/objevt-oriented programming, wouldn't it?
Compared to elisp, where I can put advice around a function that is defined somewhere else, possibly out of my control.
This is indeed the idea of aspect-oriented programming. I'm not sure where you got a different idea. Could you point me that way?
That said, thinking on some of the tricks I'm ok with in tests, I guess it has to be possible. Not sure what the code looks like.
It is amusing, as even the annotation based stuff I've seen is near universally disliked by engineers with experience from it. Seems a very common foot gun, in practice.
{ option: true, __proto__: bar }.foo(…)
To extend bar.foo(…)
?(I think you are implying that functions would care about properties of this and that one would achieve “dynamic scoping” by modifying what this is.)
The first issue is that this doesn’t go deep in the stack. E.g. if you want to affect the option used by the foo function on a whole list of objects, it is hard. If you want to affect a call deeper in the stack that can be very hard (elisp example: some extension might want to switch to a buffer but you might want it to pop up the buffer or make some other windowing choice. The solution is that by dynamically binding certain variables around where this extension/function is called, you can make switch-to-buffer call display-buffer and you can override the way display-buffer behaves)
The second issue is that it is ugly.
Compare the elisp example of:
(let ((option t))
(bar-foo bar))
bar-foo can call baz which can call whizz which could then depend on the value of option.