Reputation: 106351
I'm working on some Clojure code that has some circular dependencies between different namespaces and I'm trying to work out the best way of resolving them.
Any thoughts? What is the best way to handle this kind of circular dependency in Clojure?
Upvotes: 19
Views: 9882
Reputation: 2032
It's good to think carefully about the design. Circular dependencies may be telling us that we're confused about something important.
Here's a trick I've used to work around circular dependencies in one or two cases.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example/a.cljc
(ns example.a
(:require [example.b :as b]))
(defn foo []
(println "foo"))
#?(
:clj
(alter-var-root #'b/foo (constantly foo)) ; <- in clojure do this
:cljs
(set! b/foo foo) ; <- in clojurescript do this
)
(defn barfoo []
(b/bar)
(foo))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; example/b.cljc
(ns example.b)
;; Avoid circular dependency. This gets set by example.a
(defonce foo nil)
(defn bar []
(println "bar"))
(defn foobar []
(foo)
(bar))
I learned this trick from Dan Holmsand's code in Reagent.
Upvotes: -1
Reputation: 3440
I am having this same problem constantly. As much as many developers don't want to admit it, it is a serious design flaw in the language. Circular dependencies are a normal condition of real objects. A body cannot survive without a heart, and the heart can't survive without the body.
Resolving at call time may be possible, but it won't be optimal. Take the case where you have an API, as part of that api is error reporting methods but the api creates an object that has its own methods, those objects will need the error reporting and you have your circular dependency. Error checking and reporting functions will be called often so resolving at the time they are called isn't an option.
The solution in this case, and most cases, is to move code that doesn't have dependencies into separate (util) namespaces where they can be freely shared. I have not yet run into a case where the problem cannot be resolved with this technique. This makes maintaining complete, functional, business objects nearly impossible but it seems to be the only option. Clojure has a long way to go before it is a mature language capable of accurately modeling the real world, until then dividing up code in illogical ways is the only way to eliminate these dependencies.
If A.a() depends on B.a() and B.b() relies on A.b() the only solution is to move B.a() to C.a() and/or A.b() into C.b() even though C technically doesn't exist in the real world.
Upvotes: 12
Reputation: 17
Either move everything to one giant source file so that you have no external dependencies, or else refactor. Personally I'd go with refactor, but when you really get down to it, it's all about aesthetics. Some people like KLOCS and spaghetti code, so there's no accounting for taste.
Upvotes: 0
Reputation: 84331
I remember a number of discussions on namespaces in Clojure -- on the mailing list and elsewhere -- and I have to tell you that the consensus (and, AFAICT, the current orientation of Clojure's design) is that circular dependencies are a design's cry for refactoring. Workarounds might occasionally be possible, but ugly, possibly problematic for performance (if you make things needlessly "dynamic"), not guaranteed to work forever etc.
Now you say that the circular project structure is nice and modular. But, why would you call it that if everything depends on everything...? Also, "every time you have a dependency to resolve" shouldn't be very often if you plan for a tree-like dependency structure ahead of time. And to address your idea of putting some basic protocols and the like in their own namespace, I have to say that many a time I've wished that projects would do precisely that. I find it tremendously helpful to my ability to skim a codebase and get an idea of what kind of abstractions it's working with quickly.
To summarise, my vote goes to refactoring.
Upvotes: 28
Reputation: 49329
I had a similar problem with some gui code, what I ended up doing is,
(defn- frame [args]
((resolve 'project.gui/frame) args))
This allowed me to resolve the call during runtime, this gets called from a menu item in frame so I was 100% sure frame was defined because it was being called from the frame itself, keep in mind that resolve may return nil.
Upvotes: 15