So I’m not going to try to dispute these statements. He’s making an absolute statement (“quickly”) when speed of rot is really relative. All code rots. What is he comparing Clojure’s rotting speed to? If he’s comparing it to Haskell, or typed languages in general, I think he still has the entire case to make ahead of him.
“Nulls are a problem in Clojure” is true. Undoubtedly. Haskell’s Maybe solves some of the problem of nulls, while carrying along a lot of the downsides. So in that sense, Maybe’s “are a problem”.
Clojure is not perfect, but neither is Haskell. I think Haskell is the most interesting typed language that is practical right now. It is definitely worth learning. Understanding typed programming has made me a better programmer. I think much more systematically. And I’m appalled by some Clojure code I see, that doesn’t do anything to mitigate the risks of nulls and other pitfalls. All languages have pitfalls and they should be understood to avoid them.
What I don’t appreciate is the rhetoric around comparing language features the way the author of the Beyond Clojure series is writing. It’s just a bunch of general statements that make one language/paradigm seem superior. It just seems like grass-is-greener syndrome without much real contribution.
The tools I use are careful design and TDD for when it gets really hairy. I use those in Clojure and I used them in Haskell. The constraints are just different. In Haskell, the design decisions are about what your types look like and whether they model the problem well. In Clojure, it is the same, except more subtle, and so more error-prone. The types are in your head, and you’re also mitigating factors like
nil and whether you’ve covered all of the cases. Haskell clearly wins when the problem is simple.
But what happens when it’s complex? Your type has to get more complex. And the error messages you get from them are more difficult to understand. And you can even construct types that you can’t understand and take hours to work out how to satisfy with a program. But somehow in Clojure, those complex types have some kind of gating. I’m not really sure I have a great explanation for this. It has something to do with how you can really narrow down a type at runtime. This is how occurrence typing (the system in Typed Racket and Typed Clojure) works. It analyzes if statements and can narrow down the type of a value based on that. For instance, if I say:
(if (string? x)
I can be sure that in the then branch,
x is a string. But in the other branch, I still don’t know what it is (but it’s not a string). That lets me do local reasoning in that branch. In Typed Racket, the compiler helps you figure out if you’ve narrowed down your type enough.
Why am I not using Haskell? The overall experience of Clojure is better. Clojure has many design decisions that I think Haskell can learn from (for instance, basing everything on abstractions like seq instead of on concretions like List). And the huge availability of Java libraries, wrapped by Clojure or unwrapped. Haskell might catch up, but until it does, I still find Clojure a better fit for me.
To be honest, it’s something I revisit from time to time.