Clojure has a lot a faults (just look at some of the comments here) BUT and it's a BIG BUT for me...
Clojure is SUPER FUN :)
Over the years(15+), I've been coding in PHP (suck it haters), Go, Rust,Java,Python AngularJS+, Svelte and I can honestly say for me, nothing is more fun that coding in Clojure.
It's fun testing a function in "realtime" by just "eval" it on the spot. I don't even code "in the REPL" I just use Calva and eval inline in VSCode
Maybe it's cause it's my new toy but it really does bring back the "joy of coding" I've been missing in the other languages.
*Learning Clojure became much more easier, once I told myself "It's Maps All The Way Down :D" Sure there are stuff like atoms that make it possible to code around the "immutability" but that is like using a cheat-code to go back to the old ways. Yes there are many times when mutable-data-structures are required, but while learning, don't grab for them as a first solution !
Anywhoo YMMV but once you get over the warts (many there are), much fun and insights awaits :)
Clojure's forward-facing interface (a hundred functions that operate on one data structure) ended up breaking down for me at some point and became 10 functions on 10 data structures and those data structures became AFn, APersistentSet, APersistentMap, APersistentVector, IFn, IPersistentSet, IPersistentMap, IPersistentVector, ITransientMap, ITransientVector, IndexedSeq, LazySeq, &c, &c, &c.
Maybe I started writing code wrong. Maybe I dived too deep into the internals, Maybe it was something else. But at the end, there wasn't one data structure, there were dozens and dozens each with justifiable differences, but even so, that's not what was advertised. It took a decade to reach that point and perhaps the vast majority of folks never will.
The sad part is that I know none of it is up for change without creating a Clojure2, and that highlights the problem. Why should changing the internals need to break backwards compatibility? There is one unspeakable reason: the illusion wasn't complete and they weren't really internals to begin with.
I've only needed to dig into that occasionally for a handful of specific situations (for example, in next.jdbc, where I create a hash map like abstraction over the (mutable) ResultSet object from Java -- to paper over some nasty interop issues).
An example of clojure performance issues covered here: https://tech.redplanetlabs.com/2020/09/02/clojure-faster/
All that stuff above should be fixed at the language/stdlib level.
But what is your take on what needs fixing ? What would have been your solution for the N data-structures/ M algo problem you mentioned above ?
My "real response" to is, I haven't seen this complaint(concern) to this extend that you describe in the wild or in my own life. I've definitely not coded in Clojure long enough to have seen any of that like you have, thus far my experience has been good with 'just' using maps.
I do feel my next "step-up" would be to incorporate something like SPEC or Mali to "define/check" the fields/structure of said maps.
The mix of interfaces would be a huge pain if you needed their specific behaviours, but in my experience they've never mattered and lings like LazySeq just act as performance optimisations.
But it has also been a fun and liberating experience, after I went down the static type route for some years. I feel like Clojure just doesn’t get in my way and doesn’t force me to follow a certain style or paradigm; it provides many different choices (“providing more” instead of “taking away”) and treats you like an adult who can make responsible decisions on their own.
Although I missed the linguistic art of domain modelling through type definitions, spec gave me some of that back and even provides stuff like generative testing, which is pretty awesome. Of course, it is not meant as a replacement for static typing and has a different philosophy (see Rich Hickeys talks about it), but for me it is the best of both worlds.
The dynamic development style is something that I experienced before through learning Lisp/Scheme and it is also one of the thing that I enjoy most about Clojure. Coding “experimental”, building functions from individual pieces that I iteratively design using the REPL as my assistant (which actually means - as parent wrote - communicating with it from the editor itself, which we mostly do), feels so natural and I would miss it now that I am so used to it.
I have to disagree with this characterization. It also seems a bit patronizing.
Having more choice can be good in some cases, and having fewer choices can be good in other cases. One isn't treating you less like an adult than the other.
This is really not correct. Certain choices Hickey made, prevent you from coding as you like. E.g., Hickey doesn't allow strong reader macros or inheritance in Clojure.
Stage 1: Confused by parens/the lispiness. early uncertainty
Stage 2: learn the REPL and library functions and fall in love. irrational exuberance
Stage 3: build an unmaintainable mess and can't refactor. trough of sorrow
Stage 4: figure out how to decouple appropriately and evolve the codebase rather than refactor all the time. live happily ever after!
Lol only half joking. It really did surprise me how can something I hated some much in the beginning of learning Clojure (all the parens) has now become one of the things I most adore about the language (all the parens)
Curse of good languages.
There seems to be a pattern in the language of “a problem emerges > a community solution gains traction > Cognitect develops their own solution but its weird and undocumented”, like deps.edn over leiningen, spec over malli, pedestal over ring, etc.
Many prominent clojurists recommend deps.edn over leiningen and socket repl over nrepl, but I’ve seen very little guidance on how either actually work or how to use them.
Spec seems kind of weird and not well thought out either.
And Clojure CLI tools also seem like a total shitshow compared to go or rust’s tooling.
As a result working with Clojure feels puzzling and unpleasant, and I feel hesitant to use any community library or project in the language.
There are some within the community working on this but unless it becomes a blessed solution it can still be kinda difficult for new developers to onramp into the language.
E.g. compared to something like Elixir, the road is much more arduous with Clojure.
The problem with things like spec is it kills interest in other libraries solving the same problem. Note that Malli released after spec, and after spec all but killed off a few spec-like libraries, my general observation seems to be that many in the community have moved on from spec.
e.g. deps.edn -
there is a "Guide" https://clojure.org/guides/deps_and_cli
there is a "Getting Started" https://clojure.org/guides/getting_started (it's branded as an overall Clojure getting started but the core of deps is just the clj/clojure command line tool you would use to install clojure)
there is a deep "Reference" with a "Rationale" https://clojure.org/reference/deps_and_cli
spec
Spec Guide - https://clojure.org/guides/spec
Spec Rationale https://clojure.org/about/spec
Spec API reference https://clojure.github.io/spec.alpha/
Spec resources (brief) https://clojure.org/community/resources#spec
(Rich Hickey also gave a couple of spec talks that are on YouTube)
Also, for my money, deps.edn is way more composable than lein, does not feel weird to me at all. Ditto for spec vs prismatic schema which preceded it. I think the latter case is one where people were more miffed that the official solution displaced a community beloved tool, but to me it genuinely seems really well thought through. There are some flaws discussed in the Rich Hickey talk "Maybe Not" which led to an experimental (still beta) spec2 but spec1 I still find miles ahead of what's available for the languages I've worked with.
If a newbie is trying to debug
{:deps
{ring/ring-devel {:mvn/version "1.9.6"}}
{ring/ring-core {:mvn/version "1.9.6"}}}
=> Error building classpath. Error reading edn. Map literal must contain an even number of formsThey have to notice that while they got 80% of their maps right, the file itself if a map with 3 forms. This is compounded by the fact that there are no examples of a multi-dependency project in the getting started page! If you know Clojure, this is an easy puzzle. For rookies, they didn't use (map) so the error message will probably trick some percent of them.
I don't think the deps documentation has been validated to provide what people actually want to know. Technically it is probably thorough, but "Superb" is a high bar it does not reach.
Probably the proper approach is learning deps by copying other people. Trying to learn it from the docs didn't work for me (it did for the rest of Clojure, so that was an unpleasant change).
These issues have added noise/confusion to what used to be a much simpler ramp-up for new developers. For those who've been around for awhile, the overall experience definitely felt cleaner previously, with more obvious best paths. While it's hard to argue against more options, the effect on the ground is that it's also needlessly divided the already small community a bit too.
I've found working with Clojure to be extremely pleasant/joyous and when I've had the need to bring in someone else's code, it's been an overwhelmingly good experience, even if the library is 5 years old and untouched for the last 4. (Find a random node package that's last changed 4 years ago and it might as well be toxic waste. Find a clj lib in that condition and it's probably going to work smoothly.)
I do agree the prevalence of lots of examples of leiningen and fewer examples using tools.deps/deps.edn and clojure cli makes it hard for me, as a beginner, to know the "right" patterns to use.
I consider myself something akin to a veteran. I've been coding for over two decades. Not sure if that qualifies, but anyway.
My point is: it's been with experience that I've come to value ergonomics the most.
And that for me includes having a thriving and focused ecosystem, extensive industry penetration, good and stable tooling, lots of well known codebases learn from, etc.
It was when I was young and inexperienced that I didn't see those as the important bits. I was happy hacking on any half assed editor exploring undocumented APIs and trying to discover patterns and idioms by myself. I was happy to waste time.
I'm not anymore. That's why, while a love Clojure as a language, I don't really use it that much nowadays.
Too much friction.
Our experience is that Clojure is a very good language for newcomers to programming as a profession.
In fact, we almost exclusively hire new computer science graduates. None of them had heard of Clojure before joining, and the vast majority of them became useful in 2 weeks, and become productive in a few months. What you described as barriers are the things that got sorted out in the first day when they join.
We do not hire veterans unless they already know Clojure. These people need to unlearn stuff, some of them are resistant to changes, so we don't bother with them.
Deps is well documented.
The issue I personally found is that I needed to look at a bunch of OS project's deps.edn to see how people commonly structure things. Other than that it is a simple tool.
> socket repl over nrepl
I personally use Calva (VSCode) which just starts an nrepl based on deps.edn. When writing babashka scripts I start the repl manually and connect to it. Very pleasant experience so far.
> Spec seems kind of weird and not well thought out either.
I didn't like it at first, but once I got that everything is bottom up I had an AHA moment. Function specs and instrumentation are very powerful. Conform is basically a parser, which can give you a lot of leverage.
What bothers me about spec is that it is still not released though.
> The issue I personally found is that I needed to look at a bunch of OS project's deps.edn to see how people commonly structure things. Other than that it is a simple tool.
This seems like a contradiction, because if it was well documented you wouldn’t need to look at other people’s configs to see how to use it.
My experience with deps.edn is that every time I start a project and make a deps.edn file, I immediately draw a blank and don’t know how to structure it, so I open ones from other projects to start lifting stuff out of them.
I still don’t know how to reliably configure a project to use nrepl or socket repl without just using an editor plugin. I definitely have no idea how to use those in conjunction with a tool like reveal.
To me, none of that is simple. Simple would be like Emacs’ use-package. With that I know how to add dependencies, specify keybinds, and do initialization and configuration off the top of my head knowing only the name of a package I want to use. And it has really nice documentation with tons of examples.
Most Clojure projects are maintained by a single person - Alex Miller. Even tickets to jira are manually entered by him after you explicitly write a bug request in ask.clojure.org. Quite a closed community; no wonder many significant contributors left for other venues.
For smaller languages like Clojure the consequences are way worse.
Meanwhile in the java world you have: Gradle, Maven, Ant, etc...
> And Clojure CLI tools also seem like a total shitshow compared to go or rust’s tooling.
Wat
> Spec seems kind of weird and not well thought out either.
WAT?
Lot of open source contributors have leave the community, because there is any plan on Clojure. I guess that Rick Hickey is happy with Clojure as it is now, and this is the way is going to stay. And still no Java 8 support for CompletableFuture, lambdas interface, Stream api, java.time, no pattern matching, java records, this group feels like a group of old programmers stuck at Java 6 that cannot move forward.
Communication "style" and slow pace is frustrating sometimes but that's a small price to pay for all the positive facets of the language/community. I used to be more critical of these but I don't care anymore, the community is wonderful, the language is very usable and thriving, that's what matters ultimately.
About Datomic, I still don't get why there's no push to open-source the on-prem version, especially since nubank acquisition. I dug onto the internals a few times, contributed to some of the alternatives, read/viewed pretty much everything about it and used it for fun in toy projects, and that thing is just so versatile it makes me angry it's not more accessible and as a result not more popular. It has an incredible untapped potential. Every conj I am holding my breath hoping for a "one more thing" announcement where they'd do just that. The "alternatives" do things either quite differently on too many aspects or lack traction.
Clojure has futures, lambdas, streams, a community-maintained java.time library, core.match, and Clojure records. I'm sure there would be benefits to adopting the newer JVM-native equivalents, but I'm not as sure they're worth the costs.
I basically started out with Allegro Common Lisp on Sparc's, went to Java because it was the shiny new thing, and then in 2017 finally took the plunge and have been addicted to Clojure ever since.
Don't get me wrong: the learning curve can be steep, but the steepness - at least in my case - came from un-learning all the object-oriented stuff.
Once you understand the value of values and the simplicity it brings, it becomes such a joy to code in Clojure.
For the people on here disgusted by the JVM ecosystem: There are many alternatives for Clojure.
There's Clojure for .NET, ClojureScript in at least two flavors that compile down to JavaScript, there's a fast interpreter called babashka for scripting, there's Clojerl for the Erlang VM, there's jank - which adds gradual typing on a C++ runtime.
Oh, and there's jobs that pay really well ;)
The issue with us Lispers is that we love the language more than we do the tooling. Tooling be damned! So we all end up re-inventing the wheel for the 1000th time. We will never gain wide spread adoption, as such.
With Ruby, I have Rails. With Python: Django. With Clojure...well good luck. Every framework does one thing beautifully correct and about 10 things wrong. But hey, it's up to you to modify the framework! Because that's the Lisp way of doing things!
Maybe I should publish my bespoke framework one of these days...
Edit: if you're going to downvote this comment, I challenge you to also reply with how many years of professional Clojure experience you have.
I think your contribution would be very welcome, especially with a bit of docs or minimal examples.
In some sense, the principles he taught and aimed for were the ideal. Clojure today wasn’t necessarily the ideal.
I think there could be room for a better Clojure than Clojure, so to speak
https://danuker.go.ro/programming-languages.html#non-math-ma...
Also, it has a low frequency of bugfix commits (which may or may not be related to bugs also). (but it might be the dev experience, I have not adjusted for it).
https://danuker.go.ro/frequency-of-bugfix-commits.html
In addition, the JVM gives it great performance, and it has actual CPU multithreading (while Python or other scripting languages have a GIL). This makes it relevant in today's environment where Moore's law continues through number of cores.
Therefore I don't think Clojure will die too soon.
All I'll say is that I'm extremely grateful to Clojure for the life I've been able to live and what it's allowed me to accomplish. This is truly a language for beating the averages.
The language is not very approachable for junior programmers. You can grab a Python programmer and graft them on a Java or TS project and the same week they will deliver features and bugfixes. Reading other's people code in Clojure is much harder especially when coming from imperative world. The language is terse. The documentation is terse and lacks examples. Library documentation is insufficient or non-existent. You have to read other people's code. It is a really demanding activity aggravated by lack of static typing. You cannot just hover over a map in your IDE and see its structure. The value may have come through several transformations that add, transform and remove fields. To understand what arguments a function expects you have to decipher destructuring bindings which in real code can be ingenious, i.e. unreadable. And hold on to your butts when a clever teammate invents a flow of control macro and uses it throughout.
These and other reasons make the pool of candidates small, candidates "over-qualified" and expensive. If your expert quits, your whole endeavor is screwed.
I helped to rewrite a Clojure project that stalled due to lack of affordable talent. It's been chugging along since then at a considerable pace. The new language is very popular and much less affected by the mentioned problems.
There is a mention of Penpot in this thread. Mark my words, when it comes a time to scale, they will rewrite the whole thing in a popular statically typed language.
And if I had a tough day, my clj code will look like rubbish, while my typescript code will still look decent.
It's really not a language for big team.
In fact, at my old job, the entire team learned Clojure on the job. I was the only hire who had learned it prior to being hired. Our codebase was fine and very maintainable.
The other teams were similarly new to Clojure. Most people spent a few months doing increasingly more advanced things until they were comfortable with the language and the codebase.
The main issue for complete beginners is learning to grapple with the different development setup compared to what they might be used to, i.e. interactive development on a live system using an editor-integrated REPL. We basically set everyone up with IntelliJ and parinfer. Paredit was optional, as was alternative editors such as emacs.
IntelliJ + parinfer is a similar development experience to Python. The only part that is foreign in that case is the interactive development of a live system which the new devs quickly come around to seeing the benefits of. It helps having seniors who point out the new concepts.
Not my experience, but you do need senior devs who are good at Clojure to be reviewing and helping the juniors
> if I had a tough day, my clj code will look like rubbish
This may not be a reflection of the language
During works on I18n library with pattern interpolation in Ruby I had chosen to use lazily executed methods which could be stacked with the dot operator, giving nice processing pipelines. Around that time I was reading about Clojure and it hit me how much easier would this be in this language, with function composition and sequences, not mentioning the Delays or Futures.
I think Clojure was the first language I decided to learn before conding anything serious. It took me like 2 years to really give it a try, and leave Ruby world, so in 2013 I was making notes explaining how the basics work, and in 2014 started writing a tutorial in Polish called "Poczytaj mi Clojure" (which could be freely translated as "README Clojure" (README meaning both "Clojure, read to me" and "read me [some] Clojure". Through all 2015, during my sabatical, while sitting in a cafeteria almost every day, I coverd built-in special forms, functions, type systems, collections, sequences, macros and more, publishing it online.
In 2016 I started sharing first programs on Github. So I probably need about 2 more years to become advanced, according to "Teach Yourself Programming in Ten Years" – https://www.norvig.com/21-days.html
I remember trying Hydrox for documentation and so called literate programming approach, playing with function arguments and building macros changing positional args into named ones, using core.async and multimethods to build network bots, learning macros and protocols. I've made some free software libraries through the years, and I think I finally am able to build more complex systems.
Since I occasionally have a tendency to go into details too much, or to (re-)write things from scratch, I found Clojure to be the first language giving me enough power to finish those things in less than couple of weeks (1-2 months tops), and go back to a main project to continue. In other lanugages I tried for years I was kind of sinking into sub-projects (which needed to be taken care of) and hadn't enough energy to go back and resume works on more generic, systemic level.
Happy to stand corrected if there is such a slam-dunk showcase that I've missed, I am actually interested to dig into clojure, if nothing else as a way to deepen my understanding of functional programming.
Two "copy cats" (not meant to be derogatory) of Roam that are open source are: https://github.com/logseq/logseq https://github.com/athensresearch/athens
The underlying in memory datalog style database that can run in the browser that enables these apps https://github.com/tonsky/datascript
- https://github.com/overtone/overtone - https://xtdb.com/ - https://github.com/kennytilton/matrix - https://github.com/metasoarous/oz
Come to think of it are there many TypeScript open source apps either?
NB: Kotlin is not seen there either (fairly new) but does feature quite a few mobile apps on f-droid
In any case the thread includes now a number of good project suggestions to explore
Perhaps the lisp learning curve is high enough to dissuade those who want to contribute to a project but don't yet know Clojure?
The lack of static typing, which is so often used as an argument against Clojure, is actually what makes Clojure better. The Java projects become a morass of types scattered into a myriad of packages; no matter how experienced you are with Java. Clojure's dynamic underpinnings and its paradigm of hundreds of functions for a few data structures allows for very lean designs that are easier to wrap your head around and keep in your mental models.
The other features are just gravy: immutability built in, REPL interactivity, Transducers, Protocols etc.
I felt the pain going back to a Java project.
It's also clear (see Hickey talk Effective Programs, 10 Years of Clojure, where he surveys the room) that Clojure programmers tend to be senior (real senior not 5 years experience "senior") and a little grumpy about the state of the art, opinionated, and don't want to be cogs. This may also explain the allergic reaction among people who manage programmers/engineers. Managers as a rule (especially at certain orgs) tend to want people who are more compliant and less free thinking and likely to push back.
Anyway I think you make a good point except I disagree about its survival long term. I think there's something to be said about the value of being more popular among older more seasoned programmers vs PMs. How many PMs in the 90s foresaw the rise of Linux (vs proprietary unix and Windows), how many go overboard on "agile", how many were into ruby on rails before it picked up among programmers, etc. Managers tend to be a lagging indicator (speaking very broadly).
So for every weird, cool innovation you see, the productivity is immediately lost because people equally care about how you deal with state on app startup, or routing, or database interaction. And since none of those other things move the needle much in how you compete in your business domain, competitors who are not concerned wind up eclipsing you. While you're arguing integrant vs. mount, people are just running dotnet new and bikeshedding over things closer to the business domain.
PMs/EMs/QAs/etc. may not have technical knowledge, but they smell something off about programmers arguing about what library to use for routing. Why didn't people complain about this at their past jobs? Surely if it was important, it would have come up. They just perceive you as having hired a bunch of senior people who are incredibly slow relative to their past companies. It really breeds resentment.
I have seen clojure-first companies creatively hive off parts of their engineering org to have a separate org that is allowed to use a different stack. They're not super transparent about it to avoid a big exodus of people, but I've personally seen it happen more than once.
P.S: I like it a lot, but either I do golang or ror for web dev, or python/pytorch for data sciences/AI stuff, C# for game development. I don't think i can get more productive with Clojure in any scenario, that I'm exposed too, what I'm missing?
Frontend development is simply awesome in ClojureScript. Give me Reagent + Shadow CLJS over plain React (or React Native) any day.
What do you feel is missing? Certainly there are options (something as frameworky as Fulcro, something as barebones as a React wrapper like Reagent), so there must be some itch you can't scratch?
-core.async for concurrency that led me speed up some tasks from ~1000ms to ~40ms (well, core.async got me to about 100ms, or closer to 5x speedup on other tasks, and then other optimizations got me the rest of the way). Also using refs for some very limited data sharing across go threads (apparently actually using refs and dosync, aka mvcc data structures, is rare).
-spec for parsing a dsl and validating input
-macros for a select few syntax and core async optimizations (avoiding unnecessary go block usage)
-multimethods for extensibility
-a fair amount of Java interop to use best in class libraries
-transients to help optimize performance in some places
-even transducers a few places (maybe 1 percent of my code if that, but still)
-edn for dsl files
I sense that where people have more friction is trying to work with other people, in teams and organizations. In the right organization this can clearly work, dedicated Clojure shops, but there can be resistance in other organizations, and it has to be "smuggled in", and people worry about the ability to hire (especially for orgs that tend not to train heavily - nubank clearly trains a lot of programmers on Clojure but many shops don't want to do this level of training). And then people will avoid using certain advanced features like macros or even some functional conventions (recursion, reduce and map etc instead of vanilla loops) because they worry it will make it harder to onboard people without Lisp or FP experience.
I honestly think this is Clojure's big challenge, entrenched expectations and conventions in the industry and a desire to pull programmers off the shelf. In this talk (the one we're all commenting on) Rich and others point at the productivity and fun of REPL driven development -- finding some way to really grow that and somehow take it to the next level -- as a possible solution, to offer yet another carrot to encourage people to make the leap to Clojure. It sounds like a good idea but I have no idea what that looks like exactly.
I honestly think the answer to getting more Clojure penetration may be the passage of time, as happened with python. I know nubank/cognitect has been funding some open source work with small grants on top of Alex and Rich's work (and Stuart's?). This kind of basic grunt work can pay off long term. But it's not a sexy answer and if it doesn't work you're at a dead end.
I think my motivators to do in Clojure were edn (dsl files are a key part of what I'm building), the concurrency story being strong, Java interop for libraries (Ruby wasn't necessarily missing anything but without the Java libs Clojure would not be an option), and a general belief that Clojure's overall approach (data oriented, LISP, functional) would prove generally more productive (I believe this is true but have no evidence since I never tried building it in another language). Spec has been an unexpected benefit as it saved me from writing a lot of validation code and because it can be used as a parser for the DSL (it does most of the heavy lifting).
How has your experience with CLJS? I have been using vanilla CLJ but curious to some day try CLJS.
From what I understand, there are some applications and domains that the JVM is highly optimized for—especially long-lived, highly concurrent server processes or stream processing applications where microseconds matter in order to keep up with real-time. The fly in the ointment (for some) is that Java itself is Object-oriented, compiled, statically typed, and uses mutable objects and data structures by default. Not everybody loves those design choices. And even of those who do, not everybody finds Java ergonomic (hence Scala and Kotlin, among others).
So, much like Scala and Kotlin do, vanilla Clojure just gives access to the JVM (and its highly mature library ecosystem) in different trappings. It's a functional (rather than object-oriented), REPL-driven (rather than compiler-first), dynamically-typed, and immutable data-oriented language, with Lisp's minimal syntax and structural editing experience. Plus, the designers have invested a lot of time and their cumulative decades of programming experience into making a really solid standard library that comes with a lot of smart affordances out of the box. That combo really works for some people.
Then, folks who appreciated the conveniences and good design in vanilla Clojure started looking for that dev experience in non-JVM contexts, which is how you get ClojureScript, ClojureDart, ClojErl, Babashka, Scittle, etc. And then, other Clojure-inspired languages pop up that don't purely adhere to Clojure conventions, but co-opt significant portions of its well-designed syntax and standard library—languages like Janet (compiles to C) and Fennel (compiles to LUA) and Hy (compiles to Python).
And then, script kiddies like me come along and discover, Hey Wow, I can learn one syntax and suddenly be +80% dangerous in a dozen different environments, without having to start learning a new language from scratch in each one—from the JVM to the browser to mobile apps to native executables, I've got SOMETHING to start building on because I already know how to think in Clojure.
At this point, "Clojure" is not just "a language," it's a category of languages—just like Scheme is "a Lisp," Janet is "a Clojure." I don't think that happens with uninteresting, poorly-designed, or irrelevant languages. Clojure scratches a specific itch, and not everybody has that itch to scratch. But whether Clojure is your cup of tea or not (and obviously for many people in this comment section, it's not), I don't think anyone can debate that it's a significant project that justifies its own existence. That doesn't obligate anybody to use it or even look at it twice, but I think it self-refutes a lot of the negativity that gets thrown its way.
Well, then, MRM: Map-Relational-Mapper.
Objects / classes are not so much the problem, per se—it's specifically that ORMs fundamentally involve scattering uncoordinated mutable state throughout the application. A foundational thesis of Clojure is that mutation is a tremendous source of bugs, and should be avoided / thoughtfully limited.
Once you let the unmanaged mutation genie out of the bottle, it's almost impossible to put back in.
More concretely, I used to work extensively with Rails; I loved ActiveRecord (ORM) when I first started out—it makes basic things so easy.
Later I worked on a large Rails app supporting millions of users...we used ActiveRecord extensively, and had a very talented team. ActiveRecord worked fine most of the time, but I have bad memories of spending hours or even days tracking down user-reported bugs.
I'd try to figure out how to recreate the user's state locally, even cloning pieces of the production database to work with their exact DB data, but whatever state the program had gotten into was a large graph of (all!) mutable objects. How was that flag getting set? What code could have done it? When? And the answer is basically ANYTHING at ANY TIME that could possibly get a reference to the object. And web applications are far from the worst offenders in this space because the request / response cycle is (usually) fairly globally stateless.
Clojure is the exact opposite of that experience.
The state of a Clojure program will most likely comprised of data literals (think JSON data types, if you don't have experience with Clojure / Lisp data). Printable data literals. Connect to the errant server, serialize the state, read it from your machine, and you're there. It's coherent, stable, serializable, transmittable, simple.
Who can mutate your data? No one. You have an immutable reference (maybe others do too, but reading is a fundamentally safe operation). How does it change? Only along the explicit path of computations you're working through (it doesn't change, actually, you choose to hold onto a new reference to derived data when you want to).
Or, if you really need a mutable escape hatch (like, say, you're holding a handle to a database), every type of mutation in (core) Clojure has defined (and thoughtfully so) concurrency semantics. You won't see a bunch of notes in Clojure API docs that say things like "not thread safe" like you see in JavaDocs.
TLDR: Clojure will happily give you object-like read-only views into your database (like Datomic's `datomic.api/entity`), or help you write queries with a knowledge of your database schema, but most Clojure persistence solutions will explicitly coordinate mutation into a single 'site' because that's the only way maintain a coherent view of state-over-time. And that single-mutation-site story is the opposite of what ORMs (as commonly defined) do.
note: I'm the author of Penkala
"ORM's provide 'OH MY GOD' complexity"
(from his "Simple Made Easy" talk - I might have the quote only 90% correct, but it's pretty darn close to that).
https://github.com/metabase/toucan
But beware what you do with it. Avoid DB side effects from business logic.
+1 on avoiding side effects from business logic. This applies not just to Toucan, but to database code in general. Toucan is there to give you a way to define behaviors when interacting with your application database, for example transforming certain columns when you fetch rows from a certain table from the database. It's that plus utility functions for interacting with your database. Not really an ORM.
Either way, Toucan 2 is out now: https://github.com/camsaul/toucan2 I'm still working on fully documenting it but it's already being used in the wild and Toucan 1 will probably be archived soon