QuantumDoug
QuantumDoug

Reputation: 11

Clojure compile error for "declare" variables in deftest

My background includes 10 years of common lisp so now I am learning Clojure by writing a symbolic math package with vector (i.e. a, b, c) and Nvector bindings (ab, ac, bc, etc) in namespaces, with the print method defined for these objects.

So when I wrote my deftests in the bottom of the same file as where the binding functions, I had to write (declare a b ab) to avert compiler warnings (which makes perfect sense).

    (def G3 (doall (ga-bindall "a b c")))
    galg.core=> G3
    (+a +b +c +a*b +a*c +b*c +a*b*c +a*b +a*c +b*c +a*b*c)
    galg.core=> [a +a -a ab +ab -ab a*b +a*b -a*b abc]
    [+a +a -a +a*b +a*b -a*b +a*b +a*b -a*b a*b*c]

    (deftest galg-vectors
      (declare a b ab)   ;<=== required when in same file as definitions
      (testing "GALG Vector, Nvector and Sum tests."
        (is (= (Vector 'a) a))
        (is (= (Vector 'a -1) -a))
        (is (= ab (Nvector 'a 'b)))
        (is (= (+ 1 a ab) (Sum 1 a ab)))
        ))

Then when I cleaned up my code by moving the tests to, galg.core-test file as here:

    (ns galg.core-test (:use clojure.test galg.core))  ;;<== imports a, b, ab, etc
    (deftest galg-vectors
      ;(declare a b ab)   ;<=== must be removed when in separate file 
      (testing "GALG Vector, Nvector and Sum tests."
        (is (= (Vector 'a) a))
        (is (= (Vector 'a -1) -a))
        (is (= ab (Nvector 'a 'b)))
        (is (= (+ 1 a ab) (Sum 1 a ab)))
        ))

... then, the "already refers to:" compiler error occurs when the (declare a b ab) is present:

CompilerException java.lang.IllegalStateException: a already refers to: #'galg.core/a in namespace: galg.core-test, compiling:(NO_SOURCE_PATH:2:3)

This error seems a bit excessive, since from my thinking, "declare" is really the "promise of a binding" for the compiler, without really defining one.

Any thoughts?

Upvotes: 1

Views: 229

Answers (2)

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91534

declare is not quite that smart, it doesn't really create the promise that something will be created later, rather it creates it now, and it always creates it in the local namespace, it then leaves it up to you to make sure that it gets a value before it is used, otherwise an exception will be thrown when you try to use the unbound value for something requiring a bound value. def is smart enough to not overwrite a bound value with an unbound one in the case where it was previously defined.

user> (macroexpand-1 '(declare a))
(do (def a)) 

As you can see, declare declare creates local vars with unbound values it is this local var creation that is triggering the error you see. Because the namespace (`ns) expression already added an entry with that name to your namespace, when your expression:

 (declare a b ab) 

runs it will expand to:

 (do (def a) (def b) (def ab))

which will attempt to create a var in the local namespace named a which triggers the error because that name already refers to another namespace.

Upvotes: 1

noisesmith
noisesmith

Reputation: 20194

If it is done within the same namespace, you can def and declare in any order without error.

user> (def a 0)
#'user/a
user> (declare a)
#'user/a

The problem is that all clojure variables are namespaced. A declare creates the variable in the namespace where it is invoked. Because you have invoked use to refer to the symbols from another namespace, declaring them creates new variables galg.core-test/a, etc., which shadow the ones you were using, galg.core/a etc. The error message is telling you that an unrelated var is shadowing the one previously in scope because of use.

Upvotes: 0

Related Questions