⭐ Elisp sucks (and can we make it suck less?)
Emacs is often praised for Elisp, which is presented as some sort of silver bullet and magical tool that solves everything.
In my opinion, this raises the expectations too high, and then you end up disappointed when you struggle to do very simple things (and you even end up having impostor thoughts).
Emacs is possibly the most malleable environment out there, I use it all the time and enjoy hacking it, however I like the result, not the process. I often feel that I have to do it despite Elisp.
The purpose of this not is not just to rant, but
- share annoyances and point people at ways of coping with them
- share clues for people familiar with other languages (like python/js)
- hopefully if many people have the same problems, we can even improve Elisp language or certain default behaviours
I just started writing down reasons why elisp sucks every time I'm frustrated, so I can let off the steam instead of shitposting and ranting. It might actually result into something useful.
Some of my context.
- I'm familiar with many programming languages, paradigms and programming language theory
(not bragging or anything, I just think it makes it easier to discuss if we refer to the concepts we are all familiar with) - most days, I write Python
- TODO link on python-is-good
- TODO link on python-is-good
- I've spent a fair bit of time hacking on my emacs config and scripts for building the blog, etc, so I feel like I might be in the top percentile of Emacs users who engage with lisps
- Elisp is the first actual Lisp I've coded on. Pretty much the only too, the other one is clojure, and only since very recently.
Also some things I mention are common to all lisps, but since there are many people for who Elisp is the only one they use, I hope it's valid.
Table of Contents
- standard library elisp
- very inconsistent naming elisp
- list manipulation routines are bad elisp
- TODO
cadr
cdar
cadddr
– what the fuck. elisp dash.el
elisp the short answer is to use
- TODO
- string manipulation is bad elisp
- error handling is bad elisp
- [C] regexps are case sensitive elisp
- elisp on the other hand, in a way it's nice that it's easily customizable without having to think too hard about APIs
- [D] why elisp sucks: no
check_output
/check_call
elisp
- documentation elisp
- no types elisp
- extra links elisp
- -------------------------------------------- elisp
- TODO [C] defaul error reporting sucks elisp
- elisp Fuck Elisp, issue 20191226
- TODO [D] Actually if lisp is so extensible and you can do anything home come I can't write my emacs config in Python?? elisplisp
- [D] Output Functions - GNU Emacs Lisp Reference Manual elisplisp
- [D] why elisp sucks: buffer-size elisp
- elisp gnu.org/software/emacs/manual/htmlnode/eintr/else.html
- elisp Do you have any resources to read? I've been genuinely looking for 'modern elisp' guides, but failed to find anything decent, quite opposite, people opposing use of dash/s/etc.
- elisp Also, I had to read reasonable amount of elisp (org-mode mostly, but other packages as well), and I really fail to see features you mentioned in use. Very often it's verbose car/cdr mess lacking abstractions and basic code reuse.
- -------------------------------------------- elisp
- elisp I appreciate eshell/monkey patching/edebug, but that doesn't really strike me as that good. I mean, most modern interpreted languages have this, unless I'm missing on something?
- things common to lisp in general (mostly paren based stuff?) elisplisp
- [B] awkward indentation apparently aids parinfer in placing parens, however sometimes it results in code errors without noticing elisplisp
- [C] why lisp sucks: reliance on tabulation (e.g. if you change let to let* everything shifts) elisplisp
- [D] why style sucks: comments after )))) (on last line). too many git changes when you add one line elisplisp
- TODO lisp: discourages intermediate variables elisplisp
- [C] good parts elisp
- TODO good: parinfer sometimes is quite nice elisp
- TODO good parts elisptoblog
- TODO good things: hacking on the config while loading stuff via eval-defun elisp
- TODO why elisp is good: eshell, easy to mess with IDE elisp
- [D] appreciation why is something good is hard, you don't notice it as easy as bad things elisp
- TODO [C] Write a post comparing what elisp/common lisp offers and compare to python elisptoblogpythonlisp
- CANCEL [C] "How do we kill Elisp?" elisptoblog
- TODO when I paste stuff from org-mode source to experiment, I often end up with ruined code (as in, broken code!) elisp
- TODO [C] debugging: very often it's much easier to copy over the function and use output debug elisp
- TODO [D] try finding out how to take nth character of a string elispelisp_sucks
- [C] elisp - How to determine if the current character is a letter - Emacs Stack Exchange elispelisp_sucks
- TODO [A] I haven't written proper Elisp libraries/packages, only hacks/function/etc, so apologise I am missing out on some important aspects elisp
- TODO [A] Structure as problem– solution elisp
- TODO [B] problem with car and cdr is not that they are the standard building blocks, but that they are abused elisp
¶standard library elisp
Disclaimer: different people obviously engage with different parts of standard library so YYMV.
¶very inconsistent naming elisp
Of course naming is a hard problem, but it's not that big of an issue in some other languages:
- in C++/Java (OO languages), you can refer to the documentation of the objects' class, e.g.
std::vector
granted, someone has to generate the documentation in the first place, but in the worst case you can check the class' source code/header file - in Python/JS you can do the same (e.g. python's list docs)
in addition, you can use the REPL (something likedir/vars
) to discover which methods/properties the object has - Haskell is not OO, but you can use something like Hoogle to discover all functions that operate on a given data type
In untyped language like Elisp all bets are off, the only way to find out how to use the object are
- rely on the documentation (and it's a mess)
- read the package source code to discover all the functions available, perhaps search over variable names
this is usually what I have to resort to, even for builtin packages like org-mode
¶TODO [A] hmm how to arrange it with 'documentation' section? elisp
¶TODO [B] to be fair to Elisp, I'm finding clojure suffering from the same problem elispclojurelisp
Although naming is better subjectively (possibly because the language is yonger), it's still a mess of untyped walls of text
The nice thing clojure docs have are the community provided examples
I hope this is a constructive thing I (or someone else) can borrow from Clojure for Elisp.
¶list manipulation routines are bad elisp
This is very important because dealing with list-like structures is a massive part of any programming.
¶TODO cadr
cdar
cadddr
– what the fuck. elisp
it's beautiful that matter is made out of atoms
it's not great if you have to think about individual atoms to move your fingers
¶ the short answer is to use dash.el
elisp
I mean, simply compare its reference with the mess the builtin documentation is.
¶string manipulation is bad elisp
Similarly, something we have to deal with all the time, and especially in a text editor.
¶TODO the short answer is to use s.el elisp
¶error handling is bad elisp
And again, a very important part of programming – how to make your programs behave predictably under any circumstances?
- another excellent piece of standard elisp library: What happens if a connection error/HTTP error happens? Who the fuck knows 🤷 pic.twitter.com/ksR3C41VXk
¶[C] regexps are case sensitive elisp
https://www.reddit.com/r/emacs/comments/5jip0g/strange_replaceregexpinstring_behavior/
So, if you want case sensitive, do (let ((case-fold-search nil)) (replace-regexp-in-string "my" "your" "mycat.txt" t t) )
fucking hell.. regexes in elisp are case sensitive, and that's controlled by a fucking variable. jesus
¶ on the other hand, in a way it's nice that it's easily customizable without having to think too hard about APIs elisp
as long as the author extracted the variable in defvar, it's hackable
¶[D] why elisp sucks: no check_output
/ check_call
elisp
https://github.com/karlicoss/subprocess.el/blob/master/subprocess.el
¶documentation elisp
- Elisp docs feel really, really bad. Hard to pinpoint what exactly, but often the descriptions are vague, repetitive, and include random and not very relevant trivia pic.twitter.com/aXhYWidKLP
- I'm constantly seeing people praising emacs/elisp for its great documentation, however all my anecdotal evidence so far suggests the opposite shell-command-to-string: "Execute shell command COMMAND and return its output as a string."
Somehow I almost never manage to figure out (or at least much left often comparing to other PLs).
Thankfully, in emacs you can use find-function
and just read the source code, it's often easier.
¶no types elisp
for the context, I don't mean strict/static types or whatever. I just want something like optional gradual typing, like #mypy
yes, elisp is very dynamic and it's kind of futile (and often counterproductive) to annotate everything with types
but some things are clearly typeable
- nullable string (
?string
in JS flow) - list of 'things' (.e.g.
any[]
in typescript) - side effect only function (
None
return type in mypy)
As a result of missing types, this has to be repeated in the documentation, in vague human language.
- nullable string (
- yes, in elisp it's often easier to just inspect the object in question (e.g. in repl or the debugger) instead of thinking of it in terms of types
- yes, you can add type checking (clojure things?), but I've never really seen it done except for
defcustom
¶TODO [C] sort of hard to express… but basically elisp
maybe it's just org-mode thing since this is the only 'big' elisp system I worked with
example with org-element-set/adopt in exobrain source code
if it was language with 'real' types, it would be easy to dir() or somethign to see what methods are available
but maybe it would be less flexible, I dunno
on the other hands it means that you can always hack it somehow and then maybe figure out 'proper'
but discoverability really suffers
¶-------------------------------------------- elisp
¶TODO [C] defaul error reporting sucks elisp
e.g. try making a typo here. by default it dumps a single message with absolutely no context whatsoever (file/line number)
(advice-add #'org-org-section :befoire #'exobrain/before-org-org-section)
stacktrace is pretty useful too
¶ Fuck Elisp, issue 20191226 elisp
- fuck poor standard library and need for cl-lib
"buffers are a better abstraction than strings"?
https://twitter.com/zeRusski/status/1210254995628707840
- hmm okay this has a point, I have to think about it.
I guess it's true to some extent, but sometimes I do want to manipulate strings to make the code more pure
- hmm okay this has a point, I have to think about it.
- fuck lack of proper lexical binding
file-local variable
also not sure what was the last time it bothered me… maybe some deafults changed? - fuck lack of currying
currying : dash.elit
things, also partial/rpartial
kind of cool actually that it's implemented xxx (on the other hand would be possible in python too? with a special object or something) - fuck parentheses (admit this one is subjective)
multiple types of parens makes it much better (like in clojure)
but unlikely it's something solvable within elisp
¶TODO [D] Actually if lisp is so extensible and you can do anything home come I can't write my emacs config in Python?? elisplisp
¶[D] Output Functions - GNU Emacs Lisp Reference Manual elisplisp
¶[D] why elisp sucks: buffer-size elisp
doesn't take an argument so need with-current-buffer
https://www.gnu.org/software/emacs/manual/html_node/eintr/Buffer-Size-_0026-Locations.html
can't jump to source code because it's in C?
whereas buffer-filename takes optional argument
https://www.gnu.org/software/emacs/manual/html_node/elisp/Buffer-File-Name.html
¶ gnu.org/software/emacs/manual/htmlnode/eintr/else.html elisp
Note that the different levels of indentation make it easy to distinguish the then-part from the else-part.
ugh, this is bullshit. how the fuck is this makes it easier.
e.g. if you swap if and else clauses, indentation changes
¶ Do you have any resources to read? I've been genuinely looking for 'modern elisp' guides, but failed to find anything decent, quite opposite, people opposing use of dash/s/etc. elisp
¶ Also, I had to read reasonable amount of elisp (org-mode mostly, but other packages as well), and I really fail to see features you mentioned in use. Very often it's verbose car/cdr mess lacking abstractions and basic code reuse. elisp
¶-------------------------------------------- elisp
¶ I appreciate eshell/monkey patching/edebug, but that doesn't really strike me as that good. I mean, most modern interpreted languages have this, unless I'm missing on something? elisp
¶things common to lisp in general (mostly paren based stuff?) elisplisp
¶[B] awkward indentation apparently aids parinfer in placing parens, however sometimes it results in code errors without noticing elisplisp
¶[C] why lisp sucks: reliance on tabulation (e.g. if you change let to let* everything shifts) elisplisp
¶[D] why style sucks: comments after )))) (on last line). too many git changes when you add one line elisplisp
¶TODO lisp: discourages intermediate variables elisplisp
let bindings struct is really annoying, which discourages naming variables
собственно мотивационный пример на питоне def normalize(vec): x, y = vec len = sqrt(x ** 2 + y ** 2) if len == 0: raise RuntimeError(f'bad vector {vec}') nx = x / len ny = y / len return (nx, ny) на елиспе (defun normalize (vec) (let* ((x (car vec)) (y (cdr vec))) (len (sqrt (* x x) (* y y)))) (if (=0 len) (error (format "bad vector %s" vec))) (let* ((nx (/ x len)) (ny (/ y len))) `(,nx ,ny))) а я хочу как-то так (defun normalize (vec) (let' (x y) vec) ;; can't do in elisp?? maybe with cl-destructuring-bind... (let' len (sqrt (* x x) (* y y))) (if (= 0 len) (error (format "bad vector %s" vec)) (let' nx (/ x len) ny (/ y len)) `(,nx ,ny))) наверное это можно добиться если добавить какую-нибудь магию вроде макроса (scope ...), который эти let' правильно интерпретирует
¶[C] good parts elisp
¶TODO good: parinfer sometimes is quite nice elisp
sometimes though it arbitrarily reararnges parens. e..g I have to be really careful when pasting big source blocks from elsewhere
¶TODO good parts elisptoblog
advice-patch
¶TODO good things: hacking on the config while loading stuff via eval-defun elisp
i.e. I think people who advocate for REPL are pitching for a completely wrong thing – I want to keep my code tidy, it's just nice to execute it instantly
¶TODO why elisp is good: eshell, easy to mess with IDE elisp
to be fair, same is probably true for e.g. sublime?
¶[D] appreciation why is something good is hard, you don't notice it as easy as bad things elisp
let's compare: e.g. vimscript. When I used vim I haven't even attempted to customize it, I tried once and the whole thing was just futile.
¶TODO [C] Write a post comparing what elisp/common lisp offers and compare to python elisptoblogpythonlisp
¶CANCEL [C] "How do we kill Elisp?" elisptoblog
¶TODO when I paste stuff from org-mode source to experiment, I often end up with ruined code (as in, broken code!) elisp
example: (defun org-html-format-headline-default-function
maybe I need to disable parinfer during pasting, not sure
¶TODO [C] debugging: very often it's much easier to copy over the function and use output debug elisp
also use with-current-buffer and a separate buffer – oftern much easier than repl…
¶TODO [D] try finding out how to take nth character of a string elispelisp_sucks
¶[C] elisp - How to determine if the current character is a letter - Emacs Stack Exchange elispelisp_sucks
n case you were very concerned about national characters and precise treatment of Unicode character classes, then the only solution I was able to find so far is the Python regex library. Both grep and Perl (to my utter surprise!) didn't do the job properly.
¶TODO [A] I haven't written proper Elisp libraries/packages, only hacks/function/etc, so apologise I am missing out on some important aspects elisp
¶TODO [A] Structure as problem– solution elisp
E.g. bad docs? Enjoy easy jump-to-source
¶TODO [B] problem with car and cdr is not that they are the standard building blocks, but that they are abused elisp
in the same vein consistently using head . tail . tail in Haskell to access the third element would be insane (I'm not sure if it's actually common though)