Hi Janet, we're using clojure.spec, plumatic schema, and core.typed to get rid of that pesky dynamic typing Clojure has, but we find ourselves writing the same functions for each different type! We're slow and missing our agile deadlines, but we're unsure of how to manage our large codebase without all of spec's horrible errors. What do you suggest ? Jimmy James, Co-founder and CTO of SuperREEL
Here's the thing,
I see this a lot from clients, employers, hackers, lovers, and haters. Clojure's dynamic typing is a feature, not a bug. If you've ever written a Python script, you know what I'm talking about, but Clojure takes it much further, giving us rare occasion to check types. We can ship code without static typing, we can provide guard rails for our programs, and we can do it without an alpha version.
Programs get built
Yes, we can ship production code with Clojure. A strange bit of grumbling I hear from developers directs their angst toward not having "compile time type checking errors" when using Clojure, leading them to simply chalk up Clojure to being unusable (?). Clojure ships with a lot similar data structures as Java, for example, you can still shoot yourself in the foot with a
java.util.HashMap as the
get(key) method still returns null if the value doesn't exist. Now, if you're one of those static programming language developers opting to use something more familiar like a class/struct/union/process, Congratulations. I think you'll find a degree of rigidity not present in Clojure. Rigidity = friction and friction = lower velocity. I like to ship code, and I'm usually pretty confident my Clojure code will work because I have the REPL and an endless amount of parameter checking libraries.
We all have boundaries
I'm not talking about those boundaries! (although, do respect them, please). I'm talking about system boundaries. It's no secret I dislike the over zealous use of Clojure.spec and friends, and I hate the friction it adds development. So in the past I've recommended using it on your system boundaries, if you must. Lately, for my web apps, I've been using Malli as the type checking and coercion for RESTful APIs routed with Reitit. It's great. After that, the gloves come off, because spending the overhead of 'specing' on top of REPLing and testing Clojure code, I find Clojure.spec doesn't add much value for the time cost. Lucky for us, we get some goodies from Clojure to help with type woes. Clojure's collections, vectors, maps, and sets (and nil?), all support the same handful of functions. I've talked about how a type or interface is really a bag of functions. Replacing an indexed value in a Clojure vector is a surprisingly annoying task, but I think most developers don't realize you can actually call
assoc on a vector using the index as a key (or at least I don't see it in my Clients' codebases much).
I'm sure some people will see this as just function overloading, but I want you to know that they're god damned liars. I like to think of this as the "do nothing" option, and I have never experienced serious typing problems with Clojure. I have experienced issues with Clojure.spec itself though because Clojure.spec suffers from terminal alpha versioning.
Since its inception, Clojure.spec remains in alpha. I'll admit the stability of Clojure.spec is relatively good since it's debut. However, I do think this a sore spot, implying Clojure.spec should not be used in production, even though everyone does.
We can ship production code with Clojure, and we don't need a compiler to tell us to write our code. (But we might need one for Clojurescript ;)
Thanks for reading. Usually this is where I invite people to discuss what I've written here, but honestly I felt like this post was a bit of a dump of half-baked ideas and passion for my Clojure flow state, so maybe just subscribe for the next one, follow me on twitter @janetacarr , or don't ¯\_(ツ)_/¯ .