📚 node [[elisp sucks]]

Table of Contents

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

[2019-10-19] [how it started](https://twitter.com/karlicoss/status/1185607527460937729 )

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
  • 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.

standard library

Disclaimer: different people obviously engage with different parts of standard library so YYMV.

very inconsistent naming

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 like dir/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

hmm how to arrange it with 'documentation' section?

[2021-01-25] to be fair to Elisp, I'm finding clojure suffering from the same problem [[clojure]] [[lisp]]

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

This is very important because dealing with list-like structures is a massive part of any programming.

cadr cdar cadddr – what the fuck.

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

[2021-01-25] the short answer is to use dash.el

I mean, simply compare its reference with the mess the builtin documentation is.

string manipulation is bad

Similarly, something we have to deal with all the time, and especially in a text editor.

[2021-01-25] the short answer is to use s.el

error handling is bad

And again, a very important part of programming – how to make your programs behave predictably under any circumstances?

[2020-02-27] regexps are case sensitive

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

[2021-01-25] on the other hand, in a way it's nice that it's easily customizable without having to think too hard about APIs

as long as the author extracted the variable in defvar, it's hackable

why elisp sucks: no check_output / check_call [[elisp]]

https://github.com/karlicoss/subprocess.el/blob/master/subprocess.el

documentation

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

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.

  • 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

sort of hard to express… but basically

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

extra links

[2019-10-18] EmacsWiki: Why Does Elisp Suck

--

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

[2019-12-26] [Fuck Elisp, issue 20191226](https://twitter.com/karlicoss/status/1210252416479838208 )

  • 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
  • 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.el it 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

Actually if lisp is so extensible and you can do anything home come I can't write my emacs config in Python?? [[lisp]]

[2019-10-20] Output Functions - GNU Emacs Lisp Reference Manual [[elisp]] [[lisp]]

https://www.gnu.org/software/emacs/manual/html_node/elisp/Output-Functions.html#Output-Functions

why lisp sucks

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

[2021-01-16] 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

[2019-12-26] [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.](https://twitter.com/karlicoss/status/1210287560649584642 )

[2019-12-26] [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.](https://twitter.com/karlicoss/status/1210288518637006849 )

--

[2019-12-26] [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?](https://twitter.com/karlicoss/status/1210287248467517440 )

things common to lisp in general (mostly paren based stuff?) [[lisp]]

awkward indentation apparently aids parinfer in placing parens, however sometimes it results in code errors without noticing

why lisp sucks: reliance on tabulation (e.g. if you change let to let* everything shifts)

why style sucks: comments after )))) (on last line). too many git changes when you add one line

lisp: discourages intermediate variables

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' правильно интерпретирует

good parts

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

good parts [[elisp]] [[toblog]]

advice-patch

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

why elisp is good: eshell, easy to mess with IDE [[elisp]]

to be fair, same is probably true for e.g. sublime?

appreciation why is something good is hard, you don't notice it as easy as bad things

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.

Write a post comparing what elisp/common lisp offers and compare to python [[toblog]] [[python]] [[lisp]] [[elisp]]

"How do we kill Elisp?" [[elisp]] [[toblog]]

when I paste stuff from org-mode source to experiment, I often end up with ruined code (as in, broken code!)

example: (defun org-html-format-headline-default-function
maybe I need to disable parinfer during pasting, not sure

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…

try finding out how to take nth character of a string [[elisp_sucks]]

[2021-02-06] elisp - How to determine if the current character is a letter - Emacs Stack Exchange [[elisp_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.

I haven't written proper Elisp libraries/packages, only hacks/function/etc, so apologise I am missing out on some important aspects [[elisp]]

Structure as problem– solution

E.g. bad docs? Enjoy easy jump-to-source

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)

📖 stoas
⥱ context