19

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.

  • Basic issue is that I get a "No such var: namespace/functionname" error in one of the files
  • I tried to "declare" the function but then it complains with: "Can't refer to a qualified var that doesn't exist"
  • I could of course refactor the entire codebase but that seems impractical to do every time you have a dependency to resolve..... and might get very ugly for certain networks of circular dependencies
  • I could separate out a bunch of interfaces / protocols / declarations into a separate file and have everything refer to that.... but that seems like it would end up getting messy and spoil the current nice modular structure that I have with related functionality grouped together

Any thoughts? What is the best way to handle this kind of circular dependency in Clojure?

gunr2171
  • 16,104
  • 25
  • 61
  • 88
mikera
  • 105,238
  • 25
  • 256
  • 415

5 Answers5

27

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.

Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • 1
    Thanks Michal for the insight and useful background! I'm still not yet convinced that always avoiding circular dependencies is necessarily the best design option for project structuring. Will have a look at the Clojure group and see if that can convince me otherwise :-) – mikera Jun 22 '10 at 16:12
  • 2
    A small update - putting the protocols in their own namespace has worked well and solved most of the problems, I usually end up adding a (:use [protocols]) to most other ns declarations and everything "just works". The only thing that I'm still finding ugly to work around are where you declare a class (e.g. a deftype) which you want to reference before it is declared (e.g. as a type hint in a protocol definition!!) – mikera Jul 06 '10 at 12:42
  • 1
    Thanks for the update, happy to hear that! I think that hinting protocol/interface functions with the names of actual implementing classes may not be a very good idea, though (actually I was under the impression that protocol methods cannot yet be hinted at all, but interface methods can and the argument is the same): hint with the name of the interface instead. If you're dealing with a `deftype`-created class, all its methods will be `Object` / interface / protocol methods anyway. The only time I'd use hints pointing to classes is when that's needed for interop. – Michał Marczyk Jul 06 '10 at 22:47
  • Still, out of curiosity, how do you work around not having a class around yet when it's needed for a hint...? – Michał Marczyk Jul 06 '10 at 22:49
15

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.

Hamza Yerlikaya
  • 49,047
  • 44
  • 147
  • 241
13

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.

Ralph Ritoch
  • 3,260
  • 27
  • 37
  • 2
    Body and heart are not composed or designed to be composable. Namespaces should be. You don't get composability by just "modeling the real world". – Leon Grapenthin Apr 10 '14 at 11:33
  • 2
    Namespaces exist for the sole purpose of being able to re-use the same names in different contexts without a collision. What you get by modeling the real world is an intuitive and maintainable design. I'm not going to dispute the composableness of hearts or bodies, but there are many cases that show they are indeed composable. – Ralph Ritoch Apr 10 '14 at 12:27
  • 1
    If you are talking about namespaces strictly in the sense of avoiding name collision you should know that no dependency constraints are imposed. You can fabricate both namespaced symbols and keywords. Dependencies come with `require`. There is an order in which libs are *loaded*: LIB1 *requires* LIB2, thus LIB2 will be *loaded* as part of LIB1. Do you know what happens when LIB2 *requires* LIB1? - Of course. A solution would be to ignore that and simply wait to see what happens in runtime. Hickey commented why he chose not to – Leon Grapenthin Apr 10 '14 at 12:59
  • 3
    lgrapenthin , I read Hickey's comments and while he clearly states some advantages of lisp style, he is doing nothing more than making excuses. Declarations for third party namespaces (that lead to errors if called before defined) and softer loading rules (such as soft-require where it states the need for the feature but doesn't trigger the file to be loaded) solves every issue Hickey was crying about. It is pure lack of experience on Hickey's part. – Ralph Ritoch Apr 10 '14 at 15:28
  • 1
    Hickey states in the link that you have to consider the utility/complexity tradeoff. Have you done that? – Leon Grapenthin Apr 10 '14 at 17:26
  • lgrapenthin, I don't think the complexity is that high. I could probably do it now in pure clojure by defining the functions to simply throw errors and letting them be redefined in the correct file, and commenting out the "backward" references. Such a hack wouldn't likely compile to java though. To get the best of both worlds some amount of preprocessor control is needed. – Ralph Ritoch Apr 10 '14 at 18:51
  • I wouldn't doubt the wisdom of Rich Hickey casually. Isn't the idea of dialects of Lisp to organize logic and data into trees? That's what a linked list is. Having a dependency that is cyclical does not fit into a tree - that is a graph. To resolve the cyclical connection you must add a parent node with the dependencies under it. Thus, if two namespaces need to share something, what is being shared needs to be in it's own namespace. – Tyler Feb 08 '17 at 21:36
  • Tyler, your opinion is very political but the idea of lisp is the fusion of data and code, it was commonly used for AI and optimized for being able to recode-itself. As you can see this only weakly translated into Clojure which isn't a LISP since it doesn't comply with LISP standards. Clojure is its own language. – Ralph Ritoch Feb 09 '17 at 05:07
  • Refactoring is a fine suggestion in Clojure. However, I have long utilized shallow circular dependencies in other languages. I tend to run into the desire for them when using the Component framework, as a component is often utilized to provide state at startup time but the component relies on a namespace which also utilizes this state. Namespace splitting is an easy solution here. – ctpenrose Aug 03 '17 at 22:13
  • I'm not sure why people are so fast to judge you as an incompetent coder because you have a circular dependency that you can't easily resolve. In reality, a re-factor can often solve the issue 90% of the time, but there are exceptions to every rule. I'm sure there are some real-world examples of this out there somewhere. – Joel M May 30 '20 at 17:04
0

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.

stopthe
  • 17
  • 1
-1

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.

bonkydog
  • 2,012
  • 20
  • 11