reddit / emacs
Elisp and anti-patterns
fasttalkerslowwalker · 2022-11-14 15:59
Is it just me, or does anyone else feel like learning elisp involves using an inordinate number of anti-patterns? For example, I was trying to figure out how to call a function that could create a new org entry from an org template, with certain values passed in as parameters. I kept running into a wall, until I realized that I needed to set a global variable to a plist containing the values, and then access those values using elisp in the template. This would be a sure sign that someone doesn’t know what the hell they’re doing in any other language I know of, but it seems to just be how things work in the EMacs/elisp world. I can only imagine how bad my code would be if I learned by hacking on EMacs….
Is this just a function of EMacs being an older system that was developed before more best practices were developed? Is there some other justification for doing things this way? Or am I overblowing how often things work like this?
a-c-d-w · 2022-11-14 17:22
In my experience, org's devs in particular make some..... Interesting choices with its programming. Hacking with org feels more like hacking than with other parts of emacs.
That being said, could you let-bind the variable around the template?
ieure · 2022-11-14 17:22
> Or am I overblowing how often things work like this?
Probably.
> ...I realized that I needed to set a global variable to a plist containing the values, and then access those values using elisp in the template.
You rarely need to *set* globals, but it's a common pattern to shadow global values with let bindings. Perhaps you can adapt your code to do that instead.
Ill_Pause3020 · 2022-11-14 17:26
Do you have a (code) example? I do not recognize this uhm pattern in Emacs Lisp, or any Lisp. Lisp, Emacs Lisp specifically, have (in general) far better code patterns than most languages.
Illiamen · 2022-11-14 17:35
I don't know the features you speak about, but I would guess that such things could be from developers creating packages for their own needs first, in that working on the package is secondary to the work the package is supposed to enable.
In that mindset, it might not be suprising to find authors taking the first apparent path to get something working, instead of wondering what would make a good interface for something done years later. Especially as needs/desires change, things that were fine at the beginning might later be found to be insufficient. I think that's how we got from `completing-read` to Helm, etc.
There's also the point that not everyone that has contributed to Emacs is a programmer by profession or study. The original author of Org is an astronomer, if that's supporting evidence of what I said above.
At the end of the day, I think the deciding factor is that weirdly designed extensions are more useful than no extensions at all, so if the focus is on what's useful, and if what's implemented faster can be used more quickly and spread faster, then we should expect such "anti-patterns" to occur.
That being said, if you identify things that you think could be improved, it is worth it to contact the authors, such as on the Org mailing lists. After all, a more easily extended library can also prove more useful than a weirdly designed one.
1nc0ns1st3nt · 2022-11-14 17:40
How would this be a sure sign that someone doesn’t know what the hell they’re doing in any other languages.
Sure you can infix that global plist into your function when creating the package, but that would mean it is now hard coded. Which is now not extendable or customizable. Which is not viable from the perspective of usability.
Global variables aren't evil. Neither is eval.Computing systems are essentially state machines, a program constituted of only functions is dead. Not alive. Not even C, who has a main function as entry point. It is just that the compiler hides the initial state in main function from accessible in other parts of the program (or name-space).
In the end, the current computer environment are designed to protect ourselves from breaking the computing device by accident by layers of abstractions. Emacs just so happen to expose them to us to allow more magic to happen. Take bunnyhopping in CSGO, which are glitches from cs 1.6 and quake, most likely to be an unchecked edge case to begin with, but now the surfing and kz servers are a big part of CSGO.
ForkInBrain · 2022-11-14 18:20
> Is this just a function of EMacs being an older system that was developed before more best practices were developed? Is there some other justification for doing things this way?
For some historical context read [https://www.gnu.org/software/emacs/emacs-paper.html](https://www.gnu.org/software/emacs/emacs-paper.html) (a paper written by RMS in 1981). In it he describes the rationale for some of the choices you see. See in particular the [Language Features for Extensibility](https://www.gnu.org/software/emacs/emacs-paper.html#SEC15) section.
The needle has moved since 1981. For example, Emacs now tries hard to use lexical binding by default in new code, while leaving the the dynamic binding still possible but more explicit. However, the concept of a current context, and customization by temporarily binding "global" variables, is very much still present (e.g. buffer local variables, etc.).
And, yes, some code and designs in Emacs Lisp strike me as not great. Functions are often way too long to be easily understood, but I have heard that a practical reason for that is that they run faster that way (or at least they used to). Functions often take way too many optional args, requiring you to call them like `(somefunc nil nil t somearg)` far too often, but this often happens organically as "yet another" optional behavior seems worth implementing but is judged not worth adding a whole new function for.
arthurno1 · 2022-11-14 18:21
> I realized that I needed to set a global variable to a plist containing the values
Since you haven't posted any code, I am not sure, but it seems like you are talking about let-binding; which is not anti-pattern, but a standard lisp idiom. I am not sure, but I think you're seeing something like this:
(defvar some-var ...)
Which you can then use to let-bind (capture binding, not the value) in your code to customize its value only in that particular let scope:
(let ((some-var my-value))
(call-some-function-that-uses-some-var internally ...))
'inhibit-read-only' is for example one such variable that is often let-bound, you can search the Emacs sources to see examples of the usage.
> Or am I overblowing how often things work like this?
You are totally overblowing. You just need to learn some more lisp idioms. I think Peter Siebel [explains this really nice](https://gigamonkeys.com/book/variables.html). I really suggest reading that chapter, especially the part on dynamic, a.k.a. special variables, which is what I think you are referring to in your post.
Jack-o-tall-tales · 2022-11-14 18:41
A couple of things others haven't mentioned:
First, Emacs lisp has no namespacing, so often variables (and functions) which could exist package-locally are defined globally.
Second, remember that (unlike a lot of other software), Emacs is intentionally built to be hacked by the user, so in some ways, it is very *desirable* for important variables to be global and mutable -- so that the user can alter them in their init file. If the global variable you are talking about is a custom variable, then this is probably the case.
andyjda · 2022-11-14 18:52
I think this would be easier to reason about if you posted some code examples and explained what specifically you think is anti-pattern and why.
Are you taking issue with the idea of creating a variable and allocating memory just for one-time use? Because that does sound like an anti-pattern, but it also sounds like you could probably achieve your goal without doing that.
And there could be an arguably valid reason for the design choice (such as making a global variable available for customization). Maybe it doesn’t fit your use-case so you’d have to hack around it.
itistheblurstoftimes · 2022-11-14 19:05
>I was trying to figure out how to call a function that could create a
new org entry from an org template, with certain values passed in as
parameters. I kept running into a wall, until I realized that I needed
to set a global variable to a plist containing the values, and then
access those values using elisp in the template.
There's no way this is necessary. Can you give us an example of what you are trying to do?
noncogs · 2022-11-14 19:39
Building upon what everyone else said, I believe what you speak of extends from lisp itself. It is precisely what makes lisp so powerful and why it’s still in use today. That is its ability to do metaprogramming. Lisp is homoiconic meaning that the language itself is a data structure that can be written in that language. It’s fundamental data type is the list. Lisp means list processor. So the language can be manipulated by the language. Lol In other words it is a metaprogramming language. This means you can use the language to solve a problem and you can change the language itself to solve the problem. You’re not limited by the traditional control flow and paradigms of the usual ways we program like for loops and if statements. You’re free to make new ways of doing anything you want. This is why the language is so powerful and why it’s important to learn. From the linked list you can build any data type, data structure, control flow, or abstraction that you could want or need. This is why lisp is still used in advanced financial analysis, quantum computing, and eMacs. And this custom language is precisely what you complain about. You’ve found that the general purpose language has been turned into a domain specific language for the needs of someone else and you criticize the way that was done. That’s fine. You should just know that this is exactly what this language is for and exactly why it’s used in eMacs. And the anti-patterns you speak of don’t really apply here. It has its own but not in the sense you speak of. Yes I know what you mean and I too get frustrated with some of the hacky solutions I use. I’m just sharing why this is and pointing out the immense irony of complaining about precisely the the thing that makes emacs, emacs. This language is beyond your pigeon-holed best practices whatever that may mean and is capable of precisely what those aren’t and yes it is fully justified in its ways. Try not to love it when it’s working for you and then turn your back on it and criticize free developer’s packages when you don’t like how it was done and then extrapolate one developer’s custom implementation to criticism of the entire language or editor. You’re free to do it better and quite often that does happen. Packages evolve and get replaced, because the language is great and eMacs is totally customizable because of it. Otherwise you would be limited through the rigidity of the language which clearly keeps you feeling safe and warm and organized. It’s hilarious you state how bad your code might be if you learned through elisp when in reality learning lisp is widely considered paramount in becoming a better programmer. And it’s lessons carry over to all other languages. It’s a rite of passage to complete the book structure and interpretation of computer programs. It wasn’t taught for 20+ years at MIT without good reason. So yeah you’re “overblowing” this one. But I do agree, some peoples elisp downright sucks. Including the crap I hack together but I’m not releasing packages. And you won’t hear me talking crap on lisp or eMacs. The same crap code you find is also what makes the power of emacs that you take for granted until it annoys you.
CorysInTheHouse69 · 2022-11-14 20:29
It is not EMacs. It’s just Emacs
fasttalkerslowwalker · 2022-11-14 21:19
Thanks for the thoughtful response! FWIW, the language I’ve used most recently is Clojure(script), which, due to its functional orientation, strongly encourages pure functions rather that manipulating global state.
fasttalkerslowwalker · 2022-11-14 21:21
THanks for the correction!
[deleted] · 2022-11-14 21:27
[deleted]
tonicinhibition · 2022-11-15 00:03
You're not wrong, and I'm surprised by the reactions in this thread.
I spent 15+ years in industry, polyglot. I went through the whole functional programming enlightenment stage. I worked through SICP and used Scheme & Racket. I fell in love with Clojure about a decade ago specifically because it adheres to the Scheme philosophy. I also hack in common-lisp, and have rewritten large global-heavy dynamically-scoped Lisp code-bases.
I share your aesthetic reaction to the Emacs Lisp that I've encountered. Most of it *is* full of anti-patterns that wouldn't pass code-review in any company I've worked at.
If you read [Language Features for Extensibility](https://www.gnu.org/software/emacs/emacs-paper.html#SEC15) that was linked elsewhere you will find that RMS' best arguments for the status quo is that it's too much work to change it now. He's not wrong.
The arguments he makes about globals and dynamic scope being necessary are complete bullshit. There are clean architectures that do command dispatching. However, we're in this mess and the community has developed some conventions to keep things relatively sane.
The truth is, it all works and it works surprisingly well. Keep your standards high at work and in your personal config/packages.
EDIT: Removed unintended bashing of Emacs itself as the problem lies with proliferation of bad code on the internet.
YakumoYoukai · 2022-11-15 00:28
I love Emacs, Lisp, and ELisp (well, maybe not ELisp so much), but I have always found it ironic that the language that's the poster child for functional programming was chosen to drive an application which is entirely about side-effects and global state.
ireallywantfreedom · 2022-11-15 00:45
The functions being too long is particularly rough imo, especially in org. It really hurts the advice system when you have to advise huge swathes of code.
luiggi_oasis · 2022-11-15 00:47
So, in this case, you're setting some-var equal to my-value but only "locally" for the inner call while the original variable remains untouched?
Also why you calling it let bind if there's no bind in your expression? Isn't bind like joining (concatenating) elements to a list?
nnreddit-user · 2022-11-15 01:24
> poster child for functional programming
Haskell holds that dubious distinction. McCarthy wasn't really obsessed with eliminating side effects in the 1950s -- he just wanted a workable translation of the lambda calculus. The monad craziness only gathered steam much later with the Brits (Milner, Wadler, Jones), but didn't become the apex of programmer snobbery until the last ten years.
xenow · 2022-11-15 01:47
The only really common problem in elisp tends to be huge function blocks (as mentioned by others) - and some core commands being a bit ambiguous as to what they tend to do (heavy arglist - like bundling 10 features into one call, vs making 10 single purpose calls).
fasttalkerslowwalker · 2022-11-15 01:48
Thanks for the validation. I’m purely a hobbyist, so whenever I have a thought like this, my first question is always “am I totally wrong about this?” And I agree and totally get the point about it being too much effort to fix at this point. It seems like there are good reasons that projects like Guile Emacs haven’t gotten over the finish line.
tonicinhibition · 2022-11-15 02:52
MIT 6.001 5A, 1986: [Assignment, State and Side Effects](https://www.youtube.com/watch?v=dO1aqPBJCPg&list=PLE18841CABEA24090&index=9) (and the following lectures) introduce me to state as a necessary evil. They even talk about object-oriented programming.
ilemming · 2022-11-15 03:14
> does anyone else feel like learning elisp involves using an inordinate number of anti-patterns?
For work, I write Clojure. Compared to it, Elisp ergonomics feel like swimming with your shoes on. I can't say I don't like it, don't get me wrong, hate me if you have to, but it's a Lisp and I rather write in a Lisp than in any other non-lispy language.
But I really wish more core Emacs developers gave Clojure (or even Fennel) a heartfelt try and maybe made some improvements. Like, I would sell my dog (or my least favorite child) to have proper hash-maps in Elisp.
I don't know much about Common Lisp, can't say if it also has some weird "wats", maybe it does, and maybe Clojure too, after a few more decades would accumulate some weird shit.
But still, when I have to spend forty minutes trying to figure out in Elisp, how to update an item in an `alist` in a `let` block, because you can't use `add-to-list`, it just doesn't feel like sparking joy, as Marie Kondo would say. You have to use some weird combination of `if`, `setf` and `cl-pushnew`, which you yourself wouldn't recognize and decipher a month later.
Yes, the language is improving and slowly, steadily getting better. We have `seq-` functions now, `thread-first` and `thread-last` macros (yeah, yeah, I know about dash.el et al., but I don't want to include a third-party lib into a package just to traverse some lists. It ain't nodejs alright?). Anyway, sorry for the rant. Elisp is great (like swimming), but it does sometimes feel like swimming with your shoes on.
uita23 · 2022-11-15 03:21
It was Kleene's recursive functions that inspired McCarthy, not Church's Lambda calculus.
arthurno1 · 2022-11-15 05:12
> So, in this case, you're setting some-var equal to my-value but only "locally" for the inner call while the original variable remains untouched?
For the duration of the let-form. Observe that there might be many functions that "see" the variable and use it, not just one. It is similar to an environment variable in a shell script. We can have the original value of some environment variable set in our OS or login environment, but then in some shell script we can set it some other value, and then start new processes from that script, all new processes will inherit the modified environment variable. If that explains.
> Also why you calling it let bind if there's no bind in your expression?
It is in a 'let' form. some-var is bound to my-value in the pseudo example above.
Or I don't know, maybe my terminology is not correct. So have I learned it, from the parlance from emacs-devel mailing list, I might have picked it wrongly too :-).
arthurno1 · 2022-11-15 05:27
Pure functions mean they don't have side effects, not that there is no global state in the program. Pure functions won't change the global state, but there are still functions with side effects, otherwhise you wouldn't be able to do anything usefull (no i/o for example). You can perfectly well have global state in Clojure.
But anyway, the "anti-pattern" you seem to refer too, is actually not modifying global state. You are misunderstanding it there. On contrary, it works on local state of a global variable, and is [available and idiomatic in Clojure as well](https://ericnormand.me/mini-guide/clojure-let).
I don't know how you have written your code, but you should have used let form to init the variable to your value, so you really are using the local value, but with global name. In that way, and as the chapter in the book I linked to you in an above comment explains, you are capturing the binding, which is exactly turning a global variable into a local. Think of environment variables in OS and shell scripts. Each process can have its own value, and child processes inherit the parent's value. Same thing here.
Bodertz · 2022-11-15 05:36
>Or I don't know, maybe my terminology is not correct. So have I learned it, from the parlance from emacs-devel mailing list, I might have picked it wrongly too :-).
You haven't. The docstring for let uses the same terminology.
arthurno1 · 2022-11-15 06:39
> If you read Language Features for Extensibility
Dude, that is an old paper from 1981, it is 40 years old. ELisp has got lexical binding since that and the code base is slowly but surely converting.
Furthermore, what you & Op are doing is comparing Elisp, an embedded language made to extend an application (Emacs), to a general purpose language like Clojure or Scheme. Being a general language and an extension scripting language are two different things.
Anyway, a lot has happened since 1981 in Elisp, [you might wish to read a paper on Elisp development by Monnier](https://dl.acm.org/doi/abs/10.1145/3386324).
arthurno1 · 2022-11-15 06:48
> McCarthy wasn't really obsessed with eliminating side effects in the 1950s -- he just wanted a workable translation of the lambda calculus.
Actually, he came up with some Lisp ideas independently of Lambda calculus, search on IPL and FLPL. He said, somewhere, that Church book was so boring, he never finished it. But he was influenced by some ideas and did suggest using lambdas in '58 as in Church calculus, and he mentions it in [the paper from 1960](https://dl.acm.org/doi/10.1145/367177.367199). Seems like using symbols for functions and combining functions comes out of lambda calculus.
Whether Lisp or at least Scheme happens to be a realization of lambda calculus, or just heavily influenced, I am not so informed to answer, but there is some interesting discussion [here](https://news.ycombinator.com/item?id=20696931) or [here](https://news.ycombinator.com/item?id=22062799), or if you are interested to read more, there is a [free book on the early history of Lisp](https://github.com/amno1/as-powerful-as-possible).
arthurno1 · 2022-11-15 06:53
Ah, ok, actually didn't read docs for let :-), but thanks for letting me know.
arthurno1 · 2022-11-15 07:12
> The only really common problem in elisp tends to be huge function blocks
Namespaces and reader macros would be really nice to have, actually :).
[deleted] · 2022-11-15 07:13
[deleted]
tonicinhibition · 2022-11-15 07:15
I didn't see the date on that, I just pulled it from another comment discussing the original design decisions.
Still, OP and I are reacting to bad programming practices in the wild. It's essentially the same gripe as the one levied against web-developers - that bad code proliferates online and there are too few good examples to draw from.
There is nothing inherent in the language that forces people to program in an unprincipled way. I worked with fantastically well-designed JavaScript libraries in the early 2000s, when JS was a mere scripting language. Some of the best organized code I've written to date is in AutoLISP - a dynamically scoped Lisp with no let bindings at all, purely designed as an AutoCAD extension and scripting language.
I am sure the code is improving. Thanks for the paper! The history of Emacs and Lisp fascinates me and actually has me questioning a lot of my assumptions about practicality vs elegance.
Ill_Pause3020 · 2022-11-15 07:29
The paper predates GNU Emacs by four years, and is not talking about Emacs Lisp or even GNU Emacs, but rather TECO Emacs and MACLISP — in which the arguments RMS made do hold.
I would love to see some examples of what these anti-patterns are that people say that Emacs is full of. Either TECO Emacs, GNU Emacs or even Zmacs. Since all of those are really good examples of how to write good, well behaving code. Whereas many so called “professional” ones are really bad …
Horrih · 2022-11-15 07:41
As always it is a tradeoff between different considerations
yes global variable are evil. It can be mitigated with let bindings, but it does not change the fact that your function has side effects, those variables are optional hidden parameters which is not good for readability and makes your program stray away from proper functional programming.
On the other hand this mess offers some advantages :
- the function signature stays the same, meaning you don't break user code adding it
- it is easier to hack
This is often done especially at the command level, which is the most user facing part of emacs
tonicinhibition · 2022-11-15 08:05
You're right, I spoke incautiously. I wasn't meaning to criticize Emacs itself, but rather the code I encounter online and the haphazard package configuration snippets that are handed to me from on high via StackOverflow and Github README files.
My personal gripe is that after all my efforts, I still have 59 instances of setq in my config, mostly on variables that have made no attempt at ad-hoc name-spacing whatsoever. My *perception* of Emacs, whether or not it is deserved, is that I'm dealing with a Big Ball of Mud.
I do suspect that Emacs itself - the core of it - must be an absolute masterpiece. It has to be well designed in order to support the insanity of such a configurable environment. I really do hope to study and understand how it behaves so well.
Ill_Pause3020 · 2022-11-15 09:23
To update a element in a alist all you need to do is SETCDR it, or use ALIST-GET. No need to introduce a circus :-)
How are hash maps different from hash tables in Emacs?
arthurno1 · 2022-11-15 10:54
> But still, when I have to spend forty minutes trying to figure out in Elisp, how to update an item in an alist
> You have to use some weird combination of if, setf and cl-pushnew, which you yourself wouldn't recognize and decipher a month later.
Sounds rather like you miss some basic Lisp foo there? Has nothing to do with Emacs Lisp in particular.
Clojure is an abstraction over Java, and is not your standard Lisp implementation. It is its own language. I don't understand while people expect things to work the same in all Lisps. Lisp is not some coherent language, but a family of languages, in which Emacs Lisp, Clojure or Racket are all their own languages with their own idioms, libraries etc.
Emacs Lisp is closer to Common Lisp than to Clojure, so I suggest looking up a good book on Emacs Lisp or Common Lisp.
Anyway, altering members in a list is nothing special, you just need to retrieve the place and set it to your value. Here is one possible solution to alter an individual member by using assoc/rassoc functions (search for car and cdr as keys):
(defvar test-list '((1 . 1) (2 . 2) (3 . 3)))
(setf (car (assoc 3 test-list)) 5)
(message "Original list (changed): %S" test-list)
(setf (cdr (rassoc 3 test-list)) 5)
(message "Original list (changed): %S" test-list)
Or if you prefer some more "functional" nondestructive way for the entire list:
(message "Original List: %S" test-list)
(message "New list non-destructive: %S"
(mapcar (lambda (cell)
(cons (1+ (car cell))
(1+ (cdr cell)))) test-list))
(message "Original List: %S" test-list)
Or another destructive way:
(message "New list destructive %S"
(mapcar (lambda (cell)
(cons
(setcar cell (1+ (car cell)))
(setcdr cell (1+ (cdr cell)))))
test-list))
(message "Original list (changed): %S" test-list)
madia_leva · 2022-11-15 12:46
As others said, it seems to be quite standard in Lisps to use dynamic variables to pass values to functions "on the side", rather than making it explicit through the function argument list. For example, Clojure uses 'with-bindings'.
If it's documented on the function, though, it should be considered part of the function interface, same as types of arguments are part of the interface, even if the language doesn't support type declarations itself. Coming from statically linked languages all that is tough to adapt to, of course.
madia_leva · 2022-11-15 13:00
They are integrated in the language (the reader has support for them), they are inmutable and the standard library has very powerful and generic functions to deal with them.
Also, let, defn, ... include support for destructuring, including hashmaps. For examle:
(defn configure [val & {:keys [debug verbose]
:or {debug false, verbose false}}]
(println "val =" val " debug =" debug " verbose =" verbose))
Yes, I know. In elisp, cl-defun and pcase-let (or cl-destructuring-bind, nice name...) support these idioms, but there is an advantage in having the language to just use the same structures everywhere and consistently integrate them on the standard operations.
xenow · 2022-11-15 13:39
Touche!
ilemming · 2022-11-15 18:39
> No need to introduce a circus
Oh, yeah? You have no idea what I'm talking about, do you? So let's say I have an associative structure, I want to upsert some key/value pairs into it. In a let block, so the updated map can be used right away. How do I do this in Clojure?
(let [m {:foo 1}]
(assoc m :foo "tis foo" :bar 42))
;; => {:foo "tis foo", :bar 42}
See? It updates the existing key and adds a new one. Simple, reasonable syntax and the updated map is available right away.
Now show me something like this in Elisp.
And if you're working with a tuple that has heterogeneous key types (I know, that doesn't happen very often) but check this out:
(let [m {:foo 1, "bar" 42, 'zop 3, 16 "some num key"}]
(assoc m
:foo "tis foo"
16 "updated num key"
"bar" "bar is also new"))
;; => {:foo "tis foo", "bar" "bar is also new", zop 3, 16 "updated num key"}
Now please show me how something like that can be done without "a circus" in Elisp.
ilemming · 2022-11-15 18:54
> Sounds rather like you miss some basic Lisp foo there?
See my comment in the adjacent thread
> I don't understand while people expect things to work the same in all Lisps.
I did not say "it should work this way", I said, I wish it did. Yes, Clojure is not Elisp or Common Lisp or Racket. But would you agree that Elisp borrowed many good (and maybe a few, not so good) ideas from Common Lisp at some point? What is preventing it from borrowing some good ideas from Clojure?
Check out Fennel, which runs atop Lua engine. It's a simple language influenced by Clojure, and it's quite fun. Elisp can be fun as well, but sometimes it feels like riddled with archaisms and inconsistencies. What's wrong with trying to improve it by borrowing [good] ideas from other Lisps?
Ill_Pause3020 · 2022-11-15 23:43
Snarky tone aside, and still no circus in sight.
```
(let ((al '((foo . 123))))
(setf (alist-get 'foo al) 456)
(push '(bar . 789) al)
al)
=>
((bar . 789) (foo . 456))
(let ((al '((foo . 1) ("bar" . 42) (zop . 3) (16 . "some num"))))
(setf (alist-get 'foo al) "tis foo"
(alist-get 16 al) "updated num key"
(alist-get "bar" al) "bar is also new")
al)
=>
(("bar" . "bar is also new") (foo . "tis foo") ("bar" . 42) (zop . 3) (16 . "updated num key"))
```
ASSOC was designed as an accessor function, not as a constructor.
Ill_Pause3020 · 2022-11-15 23:52
The Emacs reader also supports hash tables natively:
```
(make-hash-table)
=>
#s(hash-table size 65 test eql rehash-size 1.5 rehash-threshold 0.8125 data ())
```
The return value can be read back, or SETQed, etc.
True that there aren't that many "fancy" functions to manage hash tables, but Emacs uses lists as the structure of choice not hash tables -- which is why you have PCASE-LET, LET-ALIST, ... etc.
ilemming · 2022-11-15 23:59
Alright, alright, fine. I definitely picked wrong hill to die on. No point of arguing about `setf`. `setf` is actually pretty cool and awesome. Still, there are plenty of wonderful cookiez in Clojure that would be nice to have similar equivalents in Elisp. For some reason I love Clojure, but, Elisp, I like. I would love to love me some Elisp, because I enjoy writing things in it. To be honest, if Elisp was available outside of Emacs, I would've absolutely used it for things it's not meant to be for.
Ill_Pause3020 · 2022-11-16 00:16
I suspect most of the things you might wish for already exist as some package.
Outside of Emacs, how exactly? I often write scripts in Emacs Lisp, e.g., https://www.emacswiki.org/emacs/EmacsScripts, and feed them to the lions...
madia_leva · 2022-11-16 16:44
The thing is that in Clojure {:a 1 :b 2} is an inmutable hashmap, which implements the interface ISeq and ICallable, for example, so everything is nicely integrated. For example:
(let [h {:a 1 :b 2 :c 3}]
(map h [:a :c :p]))
=> (1 3 nil)
This level of integration is nice to have. Also, having inmutable data structures changes things more than it seems at first sight. You can freely share structures for any caller to modify, since they would never actually modify your copy.