1

In Clojure, I'm using gen-class with a Java library. The programmer normally provides two classes that implement an interface and extend a class, respectively. The two classes are supposed to refer to each other, and it's hard to avoid this cyclic dependency given the way the library is designed.

The cycles wouldn't be a problem--the compiler doesn't have to know about them--except that I'm trying to optimize the code by judiciously adding type hints (with huge speedups). I've been able to avoid compiler complaints about cyclic dependencies by reorganizing the code, and I've reduced the problem to a single type hint:

In one source file, Foo.clj, I have this function/method, which is required by the interface the class implements:

(defn -step
  [^Foo this ^Bar bar]
  ...)

Another source file, Bar.clj, creates a collection of instances of Foo, so I have to refer to the Foo class there. In my Leiningen project.clj, I have a line like this:

:aot [Foo Bar]

I don't get a cyclic dependency error. Instead I get a ClassNotFoundException: If I put Foo first after :aot, the compiler complains that it doesn't know about Bar when it compiles Foo, because of the ^Bar type hint in -step. If I put Bar first after :aot, the compiler can't find Foo when it compiles Bar, because of the calls to (Foo.) in Bar.clj.

My current solution is this:

  1. Delete the ^Bar type hint in the definition of -step in Foo.clj.
  2. Compile both classes.
  3. Add the type hint back in -step in Foo.clj.
  4. Compile Foo (i.e. run 'lein compile` again).

This works because when Foo is compiled the second time, Bar exists, so the compiler doesn't complain.

Is there a way to compile both classes without deleting and adding back the type hint? (Or a different way that I should think about this situation?)

Mars
  • 8,689
  • 2
  • 42
  • 70

1 Answers1

1

My inclination would be to add a factory function for Foo in the foo namespace.

(defn new-foo [] (Foo.)) ; parameterize to your satisfaction

And then you could use the forward declaration technique in the following answer to get access to new-foo from Bar - Forward-declaring a var from another namespace in Clojure?

This is of course still icky - if there's any other way to break the dependency cycle you might take it. How about defining Foo and Bar in the same namespace if it makes sense? They do seem very tightly coupled, although with an abstract problem description it's hard to tell.

Community
  • 1
  • 1
pete23
  • 2,204
  • 23
  • 28
  • Thanks pete23. Icky, I'll try it. Sorry about the abstract description--trying to keep unnecessary details out, and the real class names are `Student` and `Students`, which is confusing. There are many `Foo`s (`Student`s) and normally only one `Bar` (`Students`), using an agent-based modeling lib ([MASON](https://cs.gmu.edu/~eclab/projects/mason)). Each `Student` should have a `step` method that's called repeatedly by scheduling methods in the superclass of `Students`. Maybe possible to have `Student` extend the scheduling class--but also icky: multiple schedulers, just not invoked. – Mars May 17 '15 at 02:36
  • pete23, either the compiler is confused, or I am (you can guess which is more likely). Back to foo/bar language: Foo.clj now has `new-foo` as in your answer. Bar.clj includes `(ns Foo) (declare new-foo) (ns Bar)` before `(Foo/new-foo)` in a function definition, as suggested by the answer you linked. `Bar` is listed first in the `:aot` sequence in project.clj. It compiles! But at runtime, it throws `IllegalStateException: Attempting to call unbound fn: #'Foo/new-foo`. Whaat?? Why is that unbound? I've confused the compiler by the namespace switch, I think. – Mars May 17 '15 at 03:50
  • Hmmm it suggests it isn't picking up the foo... Hang on. – pete23 May 17 '15 at 08:25