This is a good write-up. I often find with Clojure, because it’s so flexible but also because it’s dynamically typed (which is good and bad) that it takes a bit of mental legwork to get to the point where I feel like I’m using it correctly. I also haven’t been using it continuously or following the community, and best practices change so fast. It’s great to be able to get some insight from someone who’s been using it in production for 7 years.
Has anyone (I imagine the OP hasn’t) tried out Typed Clojure? How has that worked out for people?
FWIW, the author of the piece is the (a?) founder of CircleCI, which had one of the most extensive integrations with Core.typed in existence (I believe) up until last fall: https://circleci.com/blog/why-were-no-longer-using-core-typed/
Typed Clojure is cool, but it has a ways to go. Gradual typing is a work in progress at present, and I haven’t played with the stuff that’s in development right now, so when we type a library for internal consistency that works pretty well, but dealing with inference on Other People’s Code is more difficult.
Matthias Felleisen did a talk at Clojure/West about the benefits of type systems in dynamic language and outlined a few of the shortcomings of core.typed.
That said, Schema is a nice happy medium, but necessitates that you pair schema definitions with tests most of the time (and/or, run validation while you’re developing). Runtime validation is still pretty useful, and thinking about the shape of your data is always a benefit.
Most of these are excellent pieces of advice, and practices I generally try to adhere to. I don’t use schema as much as I used to, but timbre is fantastic, clojure.test is great, and all of the core points are excellent. I’ve never had cause to use agents or STM, and atoms are best used when enclosing some stateful function.
core.async is also great, but not without caveats, and it also has some strong footgun potential. I’ve written a little bit on the subject here and here, with more coming, but the tl;dr is that core.async will swallow exceptions by default and there are a few other gotchas.
Should be titled “Clojure, The Good Parts For My Specific Use Case”.
I work on an enterprise cloud database: I use the binding for conditional restarts, agents for simple async “message passing” (core.async for more complex async), and the STM for updating multiple stateful components at once; I use mount instead of component, and I use both core.typed and schema depending on what I’m doing.
pmap has been subtly broken since chunked seqs were introduced
Also, this statement needs evidence. I don’t use pmap much but it seems unlikely that it would be broken for so long, even subtly.