https://en.wikipedia.org/wiki/Hash_array_mapped_trie
(The acronym is expanded in the article, but a ways down.)
"The value of values". Indeed. Q.e.d. No notes.
[1] https://en.wikipedia.org/wiki/Game_Oriented_Assembly_Lisp
This is also the biggest weakness of Clojure (IMO). When everything is "just data", you spend a lot of time digging deep in libraries trying to figure out exactly what shape the data should take. Additionally, the shape of input data is almost never validated, so you spend lots of time debugging nasty type errors far from the place where bad data entered the program.
There have been some abortive attempts at solving this with things like spec, Prismatic Schema, etc, but nothing that has taken hold like TypeScript did with JS.
I'm still waiting for my dream language with the flexibility and immutability of Clojure, but without the pain points of an anything-goes attitude towards typing and data shape.
or that is a good spend of time where you familiarize yourself with said library (as they say, the documentation is the code!).
Usually the library is well written enough that you can browse through the source code and immediately see the pattern(s) or keys. The additional experimentation with the REPL means you can just play around and visually see the data in the repl.
A spec does similar (and it does make it easier to seek through the source to find it).
I'm fumbling at the concept of a library surface not being self-describing but I suspect I lack some concepts; does this thought lead anywhere? Can anyone give me a clue?
However, that's an extreme case imho - you do that when you can't fix that library's bug or wrong behaviour.
But for things like key names and such, i dont think this applies - those key names are part of the library's api - and i often find that clojure libraries don't document them (or do but it's one of those auto-generated docs that dont mean anything).
I'm curious what you found inadequate with the existing solutions?
I remember when I started writing Clojure I'd use stuff like Records to encode the data-shape. Paired with Protocols they're quite powerful when the interface is more important than the shape. But in most situations the flexible data shapes are what make programs very easy to extend/evolve.
I've been using Clojure for over a decade in various domains, different projects, diverse teams, etc., and quite honestly I don't even remember the last time it felt to me that way. Briefly, for a few weeks in the beginning perhaps it did. But at some point, maybe the REPL-driven workflow model internalized or something - I just don't ever feel like the way you describe. You're looking at the data as you build, so "far from where bad data entered" rarely happens - you watched it enter.
If anything, Clojure has spoiled me - I get annoyed having to dig through confusing type/data mismatch in some other languages, despite their sophisticated type systems. Uniform data structures mean your mental model transfers everywhere. You're not learning 50 bespoke APIs, you're learning map, filter, get, assoc. The real question is what failure modes you'd rather have. Clojure's tend to be runtime, but local and observable. Some type systems trade that for compile-time errors that are... also confusing, just differently. At the end of the day, sorry for a cliché, but it is a "skill issue". I can endlessly complain about my confusion with type systems, but a seasoned Haskell developer doesn't feel lost in types, just like I don't feel lost in Clojure without explicit type annotations.
I rewatch hickey talks anytime Im a little stuck on a big problem/idea and there's almost always another "ah ha" moment for me.
If the places i work were already on the JVM, i would have switched a decade ago, but I've been in .net world my whole career.
I am still a bit disappointed that they didn't change to RRB trees or copy the scala vectors instead of the built in ones. Iirc the scala vectors are faster in general while providing a bunch of benefits (at the cost of code complexity though, but even a better RRB list implementation instead of the scala finger trees would allow for that).
I wrote an RRB tree implementation in c# just for fun [0], and while they are harder than the tries of clojure, the complexity is pretty well contained to some functions.
0: https://github.com/bjoli/RrbList/tree/main/src/Collections