More than any library, though, what keeps bringing me back to Clojure is its incredibly blissful set of persistent data structures. It's a pernicious accident of history that we are taught to accept references to mutable objects as normal, inevitable, and desirable in most CS curricula. It is much easier to develop, debug, and maintain immutable data structures instead, and I consider my good fortune every time I get to debug something in Clojure rather than in some reference-laden OOP soup.
re-frame is seriously an awesome library, with incredible documentation to back it up. I remember spending a few days reading through all of it and coming out wildly impressed.
In case anyone is interested in its ideas but is stuck with JavaScript, you may be interested in a vanilla JavaScript port I've been (very sporadically) working on: https://github.com/davezuko/re-frame. It's incomplete, and definitely lacking in many areas (documentation being the obvious one), but perhaps somebody will find it useful or want to contribute back. I've personally been using it in some internal apps for a while now.
yes! We use most of these for a pretty large enterprise webapp.
> It's a pernicious accident of history that we are taught to accept references to mutable objects as normal, inevitable, and desirable in most CS curricula. It is much easier to develop, debug, and maintain immutable data structures instead
Except for trees and graphs and the like. immutability makes working with these much more complicated.
Sure, and we could argue about whether mutable code is necessary at systems layers. But at application layers (i.e., the ones most developers work at), cost of development is almost always going to come before performance, and I think it's clear by now that OOP, at least insofar as it has been instantiated by Java, C#, and friends, has not delivered on its promise to simplify application development.
I've been slowly learning Lisp to hack on Emacs, org-mode and org-babel. Main goal being to turn AWS JSON data into org-mode headings and org-babel data to do further work on.
Very much a side project and I'm a bear of very little brain, so it's taking me a long long time.
Seeing Clojure and other Lisp stuff in the wild gives me hope that I can lean on enough people's documentation and source that I can eventually learn what I need.
I know some folks sneer a little at Lisp, but I really enjoy trying to learn it. I haven't felt as motivated to learn a coding language since about ten years ago when I used Perl to transfer/munge data from different LDAP systems into Zimbra.
So, thank you for sharing, this is fascinating stuff.
Some developers I know and hold in high regard have almost venerated Lisp, so I've always thought there must be something to it. I just thought it wasn't something you could actually use IRL, but I was wrong. I found this out when I was recently motivated to learn Clojure, and now I am starting to grok their admiration.
It will depend on whom you talk to - perhaps how old they are, and certainly where they come from (experience wise, education wise, etc.)
The usual easy excuse is about parens, but that complaint has been put to rest so many times that it's not worth replying to anymore. (And people said the same thing about Python whitespace, but now Python is at or near the top of the pile.)
Clojure has the same number of parens as Javascript, C/C++, Java, and similar languages have parens+squiggle-brackets.
(print "foo") vs print("foo"). same same.
There's so much more to it than this. I can say without reservation that people who do not take a couple/few weeks to learn a Lisp just do not know what they are missing, and their arguments against it are viewed as children who lack enough experience to know what they are rejecting.
If you look at things other than function calls, it starts to look different:
let x = 5,
y = 10
foo(x,y)
vs
(let [x 5
y 10]
(foo x y))
I actually like Lisps and think the S expression syntax is one of their best features, but I'm not going to pretend there are the same number of brackets in Lisps as there are in C family languages.
I once took some small but substantial pieces of code I'd written in Java, ported it to Lisp, and counted the grouping characters in each. Java had fewer parens, but many more grouping characters {}[]() overall. I think it was in the neighborhood of 50% to 100% more.
It's easy to prove anything with a sufficiently small micro-benchmark. But "one line of Java" is not equivalent to "one line of Lisp", and even if it were, nobody writes Lisp code in a style that looks like a one-to-one mapping of some Java code.
This is like complaining that Japanese is inefficient because it has thousands of characters, or that English is inefficient because it takes dozens of characters to say what only takes a couple in Japanese. The concepts aren't exactly the same, and you can't just pick a trivial example and conclude that "this has fewer than that".
I can't reply to your lower example because it's too deep, but I would point out that clojure does not have the other symbols - equal and comma.
In your lower example, Clojure has only one more non alphanumeric character than the other example.
Furthermore, in Lisp, you always know that you have (function-name param param etc). Reading it becomes not just easier - it removes an entire layer of abstraction (syntax rules). Also, with smart editors, you gain the ability to move these around rapidly in ways that you simply cannot do with other languages.
Funny thing is, the biggest annoyance I have when not using Clojure is having to type commas between items in data structure. [1 2 3 4 5] just looks so much cleaner than [1, 2, 3, 4, 5]. I forget who wrote it, but they complained of seeing all the mouse turds "," in normal programming languages.
The clojure example was not missing the "scope brackets". That's exactly what let is. The equivalent c like in your first example should just be enclosed in brackets.
I'd bet there have been more bugs related to implicit operator precedence compared to ( ) explicit. And given that it's numeric operations, I'll bet some were very costly...
Young children tend to look with disgust upon many meals that adults find especially delicious. Is it a problem with the food, or is it a matter of experience and perspective?
I'm very tempted to give examples, but it's not worth it here. Suffice to say, this falls squarely in the category of "you don't know what you're missing".
In this long essay by Paul Graham - http://www.paulgraham.com/avg.html - if you're willing to search for and begin reading at "The Blub Paradox", you'll get a sense of what I mean.
And then if you want to really be enlightened, at least to concepts that really what matter regarding programming languages, go watch videos from Rich Hickey (Clojure).
I think I do. I've had the argument many times here, code-as-data, metaprogramming, structured-programming. Things I don't want, aren't worth the trade-off, and things I don't want to see colleagues inflicting on others.
I've already watched (almost) every Rich Hickey video (well his main "talks"). I love them, agree with the vast majority of what he says and it has shaped me as a programmer, but I don't agree with the final conclusion being to code using lisps ;)
The claimed problem that exists however is... lame. It really boils to "if it doesn't look like C, I'm not learning it", which is a fairly pathetic argument, and not one that can actually be bested with any targeted marketing or discussion.
Most developers only learn a new language because they're forced to: at work, or joining an existing project as part of their hobby, or because the library they need requires it, or because its the only option available to them. At the point, the fact that it looks like C, or doesn't, won't matter. And they'll get over it. No one claims Nginx/Apache DSL is great, or terribly C-like. Or even really claim that its un-C-like. But I'll reluctantly learn it (to a minimum need) because I want my damned site up. Most languages are learned like that.
Developers who learn a new language for the sake of learning a new language will likely not be drawn in by it being C-like syntax, or not. At least, most who've gone around that rodeo a few times will quickly realize that the superficial syntax isn't that interesting -- It takes 20 minutes to learn the syntax (maybe not APL). You're in it for the different semantics.
What you need for mass lisp adoption is to get a hype cycle going, and pathetic justifications for parenthesis isn't going to get you there. Babbling about how hyper-parameterized cooperatively consistent parenthesized datastructures will change the world is likely more effective.
> It really boils to "if it doesn't look like C, I'm not learning it"
I think it's funny that most people who complain about this are likely coming from Java, JS, or Python, not C. And they probably would suffer a lot in C...
I've made the jump to Clojure, and to be honest, it's painful going back to other languages, but not just because of the syntax, but rather the lack of expressive power, and the excess of boilerplate. I had to do some Python work last year, and it felt like coding with a straightjacket on.
I think the ideas spelled out in rationale are really what sets Clojure apart from all the other lisps.
For decades, I got excited every time a new idea around lisp or functional programming and I'd play with them for weeks. Eventually, I'd get to the big question, how do I bring this idea into development environment that solves the hundreds of other ugly real-world problems I have to deal with to make a usable application? Clojure solves that by being hosted.
Immutable data structures are great, but when it's the default, it's a huge win in cognitive-overhead. Maybe that feature isn't unique to Clojure but it's certainly not a prerequisite to being called a lips or a functional language.
Singly-linked lists are cool, but almost every other type of list is cooler, being able to read, eval and print those as well is really cool.
> Immutable data structures are great, but when it's the default, it's a huge win in cognitive-overhead. Maybe that feature isn't unique to Clojure but it's certainly not a prerequisite to being called a lips or a functional language.
Yes, indeed.
TBH, I developed in Erlang for 10 years and, as a result, developed a strong preference for "immutable data structures everywhere".
Unlike LISPs that I've seen (admittedly not that many), with Erlang there simply is no global data so no vars to change - it's all done via function parameters and tail recursion (Erlang has TCO baked in, btw).
I've used Luminus to fill that space, and I gotta say I don't think that's the best way to go for Clojure.
Getting started with Luminus was fine, but it was often very confusing and I didn't understand a lot, whereas when I added things as I needed them and checked out a couple libraries, I had a lot easier time debugging what was wrong.
IMO languages like Clojure (or Go) don’t need a fully powered batteries included web framework like Rails or Django. The approach to pick together what you actually need (router, db bits and pieces, etc) with composable libraries works really great
There are a vast many things in which I am not an expert. The pick and choose / compose your libraries / lego approach sounds great until your trying to weigh different options (of which none seem to have a wide enough install base to be truly battle tested).
Is buddy/buddy-auth the way to go for security? Or is it Friend? The former seems to receive more recommendations as of late, whereas the latter has more activity on github. These are not the problems I want to be solving when building something.
I love the language, but the quality of Clojure's web ecosystem is... questionable.
If I were to build another webapp in Clojure, I think I'd just use something widely installed like Jersey for actually interfacing with the outside world. It'll be crufty Java, but it has nice things like StackOverflow activity, massive docs, giant userbase, etc.. etc..
You express exactly what has kept me from doing webdev with Clojure, despite my (now perhaps irrational) love for the language.
I will learn whichever language allows me to get my site/app done as quickly as possible at first. Later after I have refined and refactored enough to know what's up... then I may decide to change.
Nextjs (despite the javascript), Rails (Ruby is great!... not Clojure, but pretty good), Django (Python is low-pain, low-effort), etc. are all easy quick ways to actually get something done BEFORE you really know what you're doing. This is critically important for those of us who don't spend all our time doing public-facing web apps.
As someone who tries to use Clojure everywhere I can, its very hard to beat Rails for pushing out a webapp (many long Clojure timers share this opinion). Clojure shines for web services that require lots of data processing in the server and for integrating with parts of the Java ecosystem.
I have not investigated Coast yet. I know it aims to be a Rails for Clojure... maybe it is, and maybe it would meet my needs.
But given the incredible power and expressiveness of Clojure, I find no reason to believe that the best rapid web development framework could be developed in Clojure if the right motivated people attempted to do so.
I don't know how many people (Plataformatec?) were behind Phoenix, but even in its early versions it was comparable to Rails. In some ways, it was even better.
There seem to be two obstacles to doing this in Clojure. The first is that people who actually know enough about Clojure to do this are actually very busy (happily) doing Clojure for work. They don't have the personal need to build a Rails for Clojure. The second is that those who would be capable already know how to pick and choose the libraries and build their own (even better, more suited for the purpose) ad-hoc frameworks for their projects.
I suppose it could be a curse of the power of Clojure that the lack of language pain points lessened the need/motivation for experts to build their own RAD framework.
I'm not denying that something better than Rails can be built with Clojure, just implying that it doesn't exist, so for quickly pushing out a CRUD webapp multiple times without repeating yourself again and again(avoiding all the trivial/tedious stuff), there is Rails.
Yes, and this trend in the Clojure ecosystem unfortunately works against widespread adoption. Works-for-me, everything-is-a-snowflake reinforces the image of Lisps being creative tools for individual artisans rather than rapid development tools for teams.
Every Django/Rails app with any significant business complexity I've worked on has been quick to start but becomes more and more difficult to change over time. The reason Rich Hickey's "Simple Made Easy" talk hit so hard for me was precisely the pain from working with Django apps (and Rails to a lesser extent).
It's nice to get started on a project quickly, but often that quick start is purchased at the cost of a system that is tied together in such a way that it's very difficult to change. Human beings naturally prioritize short term benefits over long term benefits. But developers have a responsibility to their employers / clients not to pick something that makes the dev's life easier in the short term by significantly raising the cost of adapting to changing requirements in the long run.
My life has been a lot simpler since I've moved away from Django to Clojure. It's less evident in weekend projects or in the first few months of a project. But I never find myself saying to a client request "unfortunately, the way Django works, it's hard to [retroactively produce reports/get the state of the application at previous point in time/fix application logic then correct a corrupted table/upgrade just by bumping version numbers/use multiple databases/not systematically overwrite data/etc]."
Yep, that's true, should have clarify that I was talking more about fire and forget CRUD apps (small business stuff).
OTOH, there a huge companies with successful rails(github,airbnb)/django(instagram) apps, but maybe they get away with it because the have big teams to re-architect anything.
What is your preference to interact with databases in Clojure? hugsql, honeysql, clojure.java.jdbc ?
I generally agree with your statement, otoh I have used some libs that are not being actively maintained for years and they still work, out of the box.
Clojure strength is its weakness.
But yes, there is a long way to make it more beginner friendly
Are either commonly used in the way Rails, Spring, and Django are? Clojure is niche but Go is big and I don't see many webapps powered by Go like they are in Ruby/Java/Python with Rails/Spring/Django. In the web world most Go programs I see are API backends.
Here's why I think "just use composable libraries" is a bad idea:
First it isn't very friendly to new developers. You tell them to go out there and find their libraries and they have zero clue where to start.
The second problem is its not very friendly to developers with a couple years experience either. Frameworks take care of so much stuff that most people don't know they need to take care of. Not just obvious things like connecting to an RDBMS but also very basic stuff that every website will need like CSRF protection, session management, etc.
And finally, when you rely on a wide variety of micro libraries you're actually relying a wide variety of other developers. What is going to have more support and development in the future: 10 libraries split across 10 developers with varying levels of commitment, or 1 framework with a group of developers working together on it?
You don't need to build your framework like Rails or Django. You could make it easier to plug in other libraries. But it should have a sane default for everything it needs for the benefit of both new and intermediate developers. Spring kinda works like this. It's easy to use Spring libraries for different parts but you can also just use your own.
This is in constant discussion in the Clojure community but some of the draw backs of frameworks are that you start seeing libraries and code that only work with the big framework
Even when they could have worked without it, and they over Shadow competitors in the space, why use that shiny new router library when my framework already has an one that's "easy" to reach
And then of course the worst thing to happen to a code base, the framework it's built on falls out of favour and you can't hire devs or get security patches for it anymore
"don't put your eggs in one basket", and "developers know the benefit of everything and the cost of nothing" applies here
I think if a framework is to be successful in Clojure it needs to not be intertwined with itself in the "simple made easy" sense
Additionally if the "interface" is data like you might get lucky enough that you get multiple implementations, like many cljs libraries support the react interface
With declarative programming we could certainly keep the intention of code the same whilst changing out the implementation but it would require careful coordination between library creators and a open data interface so that new requirements can be satisfied later down the line and even then multiple variants to serve completely different approaches
It sort of guarantees that in a couple of years time half your stack will then be unmaintained, though (at least in my 10 years of Clojure experience).
That’s not limited to Clojure nor does it matter whether it’s a batteries included framework or not. I’m seeing this more and more, to the degree that often I’m back to rolling my own instead of relying on a library or framework that has a high likelihood to f being abandoned.
More and more I'm of the opinion that I don't want to use a library that's not included in the core product. I've had way too many dependencies become end-of-life before my application was EOL, and I ended up having to fundamentally change my application, putting in hours of work just to keep things the same, or rebuild a feature. I appreciate Rails for all the built-in stuff it provides but anything outside of that, as time goes on you're getting more and more likely to end support. Paperclip hit me hard, and my Rails apps are stuck on an unsupported library because it's not worth the time or effort to migrate to ActiveStorage. God help me if Devise is deprecated.
That's the reason I've tried to stay away from the current Javascript madness. All of modern Javascript is built on hundreds of NPM packages that will disappear in two or three years.
I certainly will never use any language or framework in production if the main feature is "nothing is included, bring your own core features" like Flask or Express unless I'm committed to building those not-included core features myself (which I probably won't). Because if I'm bringing in someone else's package, even something that's well supported by a great and developer-friendly company like Thoughtbot, that package will likely EOL before my app does. Yes I'm still absolutely bitter about Paperclip being deprecated.
All of modern Javascript is built on hundreds of NPM packages that will disappear in two or three years
This is soooo true! I actually really like Javascript but NPM is in such a bad place and it's having effects outside of just it's ecosystem. We just built an app using Nativescript which by itself is actually pretty decent but we chose to use a plugin for a specific feature we needed. But then we kept running into problems with it and, after serious back-and-forth with the plugin maintainer, it turns out his plugin depends on another plugin that has had a known, very serious bug. And that project's maintainer has known about it for at least nine months but he has said he doesn't have time to fix it. I take that to mean he's lost interest and the project is effectively dead unless someone else comes along to take the reins in maintaining it. Which we assume means the original plugin that we chose to use is also effectively dead because it so heavily relies on the other plugin.
In the end, we just removed all of the plugins as we determined they were hostile to our productivity and routed around them by rolling our own solutions.
I get it, you want to be up to date all the time, but often the case is that lib is just done, it does what it says it does, so that is why lib was last updated x years ago.
This might have been true back in the day before security issues were a thing, but it no longer holds true.
Particularly the case in public web apps.
If someone isn’t maintaining it, don’t use it.
(Obviously it depends; for a test framework, clearly not true, but for example for the “LEGO block” you pick for auth, or say, XML parsing... yes, it matters; the point here is specifically that the “take what you want” approach to a web framework results in scattered maintenance models for different components, and that is categorically bad for long term maintenance of a web app)
If there is no activity on the repo it doesn't mean that someone is not maintaining it.
Oh, like framework developers always know which LEGO block to include in their framework. Like Rails had no vulnerabilities in the past.
> If someone isn’t maintaining it, don’t use it.
Well, good luck with not using 3/4 (or even more) of software in existence. I bet the kernel that is running on your laptop depends on the software that hasn't been updated for 30 years.
This seems common in the Go culture, but I don't see at all why the language points in that direction (apart from Go having plenty of that in a barebones form in the std lib, of course).
What language can't compose libs?
The main problem is whether developers can.
To me this often is "mental dark mode". You think you're more efficient, but usually aren't.
One reason I like Elixir's Phoenix is that it's reasonably easy to leave out the heavy-weight batteries I might not want, specifically the DB library and and JS build pipeline. I also like that router is pretty simple, but can handle complex REST-y things when you need it to. It doesn't feel, to me, either bloated or bare-bones, which is nice.
all that to say, if there is in the Clojure ecosystem analogous, I'd like to hear about it.
We used om before om.next was a thing (back in 2014). Seeing the abandoned state of om and om.next and how it even had trouble keeping up with the latest React releases, and after maintaining a fork for a few months we switched to TypeScript. Burned once; not again.
I'm thinking of writing a project with a similar stack, is there any chance your code is open source? Also, what's your experience with Fulcro been like? Do you have any thoughts about it in comparison with other Clojure SPA libraries like re-frame?
Projects are closed source, so I can share only some general architectural examples.
Experience was like.. two weeks of hair pulling, gallons of coffee and no weekends, followed by intense relief and gratitude to fulcro author Tony Kay (also pathom author Wilker Lúcio) for all that not always obvious things which make great sense in the long run.
This happened to me with several great Clojure things, so I immediately knew good experience was just around the corner.
What made you choose pedestal over compojure/compojure-api or other alternatives? Similarly, why fulcro3 over something like reagent/re-frame. I have used the latter choices and am considering switching for the my next project.
I was mostly ok with compojure, but ring was driving everyone mad for big applications with lots of wrappers and handlers, with exploding complexity as APIs grew.
Pedestal, on the other hand, is offering compact single data structure with linear complexity increase, plus very neat async functionality out of the box.
As for fulcro3, I have chosen it first because it was kind of successor of om.next, and in om.next I was in love with its transactional data model and all that pull syntax, - fulcro3 turned out to be even better with that.
I was and am still using reagent for simpler setups, but query language and transactions is what helps to keep sanity with larger projects.
Even worse is having been through the enlightenment and then witnessing people talk about Javascript or Scala like it is still 1990. You just want to yell to them "You're all living in a cave!"
Even if you decide to do OOP code, it's better in Clojure, I think. Being able to use Protocols and Multimethods and `extend-type` allows to write OOP code in a much cleaner and clearer way than something like Java...at least I think so.
Navigating that slide deck was surprisingly frustrating. Clicking forward 10 times to get through a single slide must have been really annoying to present, too.
Here is a link to the website of the library that they use, its pure chance that i was watching a talk with one of the creators of this library before i saw this at the top of hacker news.
I love Clojure and take every chance to use it, but unfortunately it’s very much behind the curve. Building web apps is more and more about tying together managed services with short scripts (aka serverless). This means that most app developers never have to deal with the hard technical problems that Clojure was designed to solve and Clojure’s slow startup time on the JVM makes it unfeasible for serverless functions.
Clojurescript on Node could be a good contender, but last time I tried it, the development experience was very poor and the power of Clojure doesn’t really shine in serverless functions, so I just use Python for that which is a perfectly adequate scripting language.
A slight tangent but are there really signals that indicate AWS Lambda style serverless is becoming a dominant way of making web backends? Especially since the AWS flavour is so developer hostile (APIGW, CloudFormation, highly divergent dev vs deployment setups complicating debugging, etc)
It's fairly painful compared to a normal backend, and it seems like the serverless advocates are on the super high and super low end: big product teams that can justify microservices and the associated high friction, and on the other hand users/use-cases who recoil at keeping a monolithic server turned on all the time even on a wimpy cloud service.
But if you've jumped on the bandwagon, look into Datomic - Ions are the Cloure-native Lambdas there.
Yes, I believe so.
We've just passed the peak of inflated expectations on the technology hype curve, and the comments on HN reflect this.
But in general -
Do people manually deploy MySQL, Postgres, or MariaDB on EC2 servers? No, (most) people use RDS. The servers are still there, but the management is less.
Do people manually deploy RabbitMQ? No, (most) people use SQS.
Cloud providers have been working out of the same playbook for years - provide low-level compute, watch what people build, and create managed services which solve common use cases. Developers love these services, because it's less infrastructure to be on call for, and they can build products faster.
Lambda and Serverless represents the inevitable conclusion of the natural evolution of the cloud. Parts of it are still painful, but undoubtably it's getting easier and more capable.
Sure but RDS, SQS etc make your life simpler and easier, and they work fine with server-ful backend on eg Fargate. I think serverless is still far from providing the same net positive value.
The value of serverless is in commodified operations: once you wrote the code, it’s your favorite cloud provider’s problem to run it. This lets you provide high-availability services without a 24/7 on-call rotation.
RDS and Fargate are not serverless, bc you still have to think about scaling strategies, appropriate network topologies and you might still have to wake up at night to tweak JVM or kernel settings in your containers.
Isn't tihs exactly the other way around in serverless vs container-based backends? Services like Fargate, GCE Cloud Run, App Engine, Heroku etc are good at "just running" your code and keeping it running. Serverless systems like Lambda, Azure Functions, etc need so much plumbing, devops, ci etc overhead around them that they are full time work for someone in a project.
Clojure uses too much runtime reflection. I tried many iterations of compiling different server projects with Graal and none worked. I could get simple programs to compile with an older clojure version, but nothing with recent code.
I'd expect the tooling will improve over time. This obviously isn't an immediate solution for running Clojure in situations where cold starts matter, but in general I think many of the improvements coming to the JVM in the next few years will pay dividends for Clojure.
Provisioned lambdas make sense if you set the capacity to your median load to save money. But you still want the snappy horizontal scaling of lambda for loads above the median.
What I'd really like to see is a full stack clojurescript framework like next.js or meteor. I think fulcro comes close but it uses JVM clojure on the server side, so it's not entirely possible to re-use code on either end.
> I think fulcro comes close but it uses JVM clojure on the server side, so it's not entirely possible to re-use code on either end.
You can reuse an awful lot of code between Clojure and ClojureScript using cljc files. In my experience it is rare to have code that you want to reuse between the browser and server that you can't make portable.
The vast majority of business logic code is portable between Clojure and Clojurescript, and the cljc file extension allows the same source files to be used on JVM or in the browser.
Even asynchronous code with core.async is portable! In browser, it will be single threaded while on JVM it will be multithreaded, without any extra work from you.
Pretty simple Webapp that you could have done 7 years ago. That not necessarily a bad thing but I wouldn't develop a webapp with those tools.
Specifically the 'Component' library. I really didn't enjoy working with it and it one of the most complex to understand parts of our web application.
So while this seems like a fun set of slides (I have not watched the presentation yet), it's not what I would call a great introduction to Web development in Clojure overall.
What a surprise that the stateful edges of the application are the most complex! I didn't mind component in the past but we just started a project with mount and I really dislike working with that. Seems like there are a lot of ways to shoot your own foot with any of these libraries because there is no "one true way" to integrate them into an application. I haven't tried integrant yet but everyone and their dog seemed to be using it at the 2019 Conj.
I would totally start a project with pedestal today though.
Its composability is a bit of a double edged sword. While it leads to a more declarative style of state management, I've found our implementation of it to lead to confusion about when /where/which states are swapped in or out. But honestly I think anyone can have a great or cruddy experience with any state management library depending on how it's weaved into the code, and I don't blame the library so much as I blame our implementation thus far.
From my experience with mount, smaller blocks composed in higher order functions which except/use parts depending on config maps etc. are key to leveraging it. It's amazing to have an app built up of composable dependencies which just click together depending on your codes dependency tree though. I found the win was thinking about what exactly was needed for each part of the app and then not fighting but embracing that tree.
Clip looks interesting, although my favourite 'component' alternative is Integrant[1] -- your config can be just EDN and your start/stop functions are added as multimethods. Its beautifully little ceremony. Incidentally, the Duct[2] framework which builds on top of Integrant is my favourite way to get complex applications running. Currently using it for a number of different services and its a very pleasant experience. (I'm sure clip is too, Juxt do good work)
Clojure and Elixir (Erlang) are my favorite languages. When you get to the point where the parens and brackets just melt away and you have this awakening.
It makes me wish José (creator of elixir) would have chosen Clojure syntax instead of Ruby.