Neoasimov
Neoasimov

Reputation: 1111

Clojure: cross-referenced vars, declare and compilation errors

In a namespace, I am defining two vars (amongst others) which are maps:

(declare bar)

(def foo {:is-related-to bar})

(def bar {:is-related-to foo})

Because bar is not existing when I define foo, I am forward-declaring it using (declare bar).

There is no issues so far, everything is working as expected in the REPL.

The only thing we notice is that when I check foo in the REPL, I see that bar is unbound, which I think is to be expected with the usage of declare:

#<Unbound Unbound: #'user/bar>

The problem arises when I try to compile the software with lein jar or lein ring war (since it is a Ring application). The error I am getting from the compiler is:

Exception in thread "main" java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: Unbound: #'user/bar, compiling...

I think this is to be expected as well since I don't think the compiler can handle unbound vars.

In any case, if all these behaviors are to be expected, why are people using forward-declaration if it can't be compiled? I am probably missing something here.

Upvotes: 2

Views: 134

Answers (2)

Ivan Pierre
Ivan Pierre

Reputation: 818

If you want to refer to the Symbol bar you should use #'bar. When you use bar you have the value of bar witch is not yet defined. If you use #'bar you refer to the bar symbol witch you can evaluate when you want and will be properly defined...

(declare bar)

(def foo {:is-related-to #'bar})

(def bar {:is-related-to #'foo})

user=> foo
{:is-related-to #'user/bar}
user=> (:is-related-to foo)
#'user/bar

Upvotes: 2

Alex
Alex

Reputation: 13941

You cannot construct circular references in this manner. The reason it doesn't work is because def evaluates the form that is passed as the binding, and evaluating a symbol that resolves to a Var gets the current binding for that Var. In other words, what get put into the map foo is not a reference to the var bar, it's the value of bar. Redefining bar after the fact doesn't affect the value of foo - that's the point of immutability in Clojure.

Forward-declarations are typically used to allow circular dependencies between functions. The following works, because the the body of the function is not evaluated until the function is actually invoked; when the function is defined, it is indeed a reference to the Var that gets compiled.

 (declare bar)

 (defn foo [x y]
   (bar x (* 2 y)))

 (defn bar
   ([x] (foo x 3))
   ([x y] (+ x y)))

Upvotes: 6

Related Questions