First, a disclaimer: the opinions on these posts are my own, and they reflect (for me) a design decision on the language that I don’t understand, specially considering other decisions that seems to contradict it. I also want to say that Clojure (and ClojureScript) is my favorite language, the one that I enjoy writing on my free time and professionally, so by no means this is a rant on the whole language!

Well, this is a new “series” on this blog: what is on the Clojure language that I don’t like, that I feel is out-of-place, and sometimes I can’t understand? In this first post, “keyword inheritance”. And what is that?

Clojure allows us to use derive to generate a “parent-child” inheritance against keywords. So, for example:

(isa? ::dog ::animal) ; => false
(derive ::dog ::animal)
(isa? ::dog ::animal) ; => true

This will change the way multimethods work too: so, for example, if derive is used and a multimethod expects an ::animal and you send a ::dog, it’ll use the implementation for ::animal:

(defmulti cry :type)
(defmethod cry ::animal [_] "Some animal crying")

(cry {:type ::wolf})
; Execution error (IllegalArgumentException) at user/eval152 (REPL:1).
; No method in multimethod 'cry' for dispatch value: :user/wolf
(cry {:type ::dog})
; => "Some animal crying"

Why it’s strange?

Clojure is a functional language. It avoids mutable data (mutability should be controlled with atom, agent, binding, etc and everything is immutable by default) and global state (everything is inside a namespace, for example, so “global data” is local to the namespace). So derive, for me, is a strange beast on this world: it is global state, and it is mutable, and affects the whole language. Worse yet, you can underive things, making multimethod dispatch quite unpredictable if you use both. It also make inheritance even worse than it is on object oriented world: at least, inheritance depends only on the moment of creation of a class, but with derive any keyword can inherit from any other keyword at any time! You can also conditionally derive something, making inheritance depends on run-time conditions. Not even Ruby does this!

(Now, just kidding: it does in some places!)

Why is it bad?

First because some libraries will try to use it: for example, Integrant respects the keyword inheritance; but the worst part is that it spreads rules about how a program works in multiple places of the code, so even (require ...) can change behavior of existing code, code that doesn’t even know that the required namespace even exist!

But I think the worst part is that it is unnecessary. Instead of doing something like the multimethod dispatch example on the beginning of the post, you can make the dependency explicit:

(def inheritance {::dog ::animal})
(defmulti cry #(let [t (:type %)]
                  (inheritance t t)))
(defmethod cry ::animal [_] "Some animal crying")

(cry {:type ::dog})
; => "Some animal crying"

The same behavior, but now is local, and immutable. And, even then, I still think there are better ways of solving this kind of code.

What about def?

Yes, def is somehow a “global state”. And yes, you can “mutate” the vars in a namespace by issuing multiple def in a single namespace like:

(def a 10)
(defn inc-a [] (inc a))
(inc-a) ; => 11

(def a 20)
(inc-a) ; => 21

But the behavior above is not stable: it can cause problems if you AOT (compile, create an uberjar, and so on) your code, and also, I don’t know anyone that uses this kind of “feature”. One can also say that def mutates the current namespace, and it is almost true: in fact, when the Clojure reader finds a def anywhere in a namespace, it will generate an clojure.lang.Var$Unbound – an Unbound Variable – and it’ll wait for the code to define its value. So, if you use (def a 10), you’re already defining the value for the var a. It is almost the same behavior for declare, in fact.

But by definition, if you have two namespaces – parent and child – and you require child from parent, there’s little you can do from child that will affect the parent namespace: specially because the child have no way of referencing the parent, as Clojure doesn’t allow cyclic dependencies.

Now, the same is not true for derive – if the parent have some multimethod, the child can easily derive a symbol and change the behavior, and the parent have no way of knowing it! And, if imperative programming taught us anything, is that mutating things that influence changes in behavior is a really bad idea.


0 Comments

Leave a Reply

Your email address will not be published.

%d bloggers like this: