1
votes

I'm starting to get more into Clojure-Java interop. If I create a Java class in Clojure, I need to import it, but if I merely use fields or methods of a class, I don't have to import it. For example:

(ns students.Student
  (:import [sim.util Double2D])

(defn -step
  [this students]  ; students is a students.Students, extends sim.engine.SimState
  (let [yard (.-yard students) ; a sim.field.continuous.Continuous2D
        yard-width (.-width yard)
        yard-height (.-height yard)]
    (.setObjectLocation yard this (Double2D. (/ yard-height 2) (/ yard-width 2)))))

This won't compile without importing Double2D, because the code creates a Double2D instance.

However, the fact that I access the yard field and setObjectLocation() method of a Students instance, and the width and height fields of a Continuous2D instance causes no problems, even though I don't import the Students or Continuous2D classes.

Does this mean that Clojure is using reflection at runtime to access Java fields and methods? Is that inefficient? If I need speed in a function, could it be advantageous to add type declarations for the Java classes, along the missing import statements? Would that prevent reflection?

[EDIT: As my second comment on Arthur Ulfeldt's answer suggests, I now think that any inefficiencies that come from not knowing classes at compile time are likely to be not very different from inefficiencies that come from the fact that functions are contained in variables. If I was worried about that, I'd forget about Clojure and program in pure Java (maybe), instead of trying to use Java libs from Clojure. For me, a world in which I can use Clojure instead of Java is a better one.]

1

1 Answers

4
votes

Import is only used to make it so you don't have to type the full name of the class, including it's package name, each time you want to use it. if a class is on the classpath you can call it by it's full name from anywhere. If you as an import statement you can call it by just the class name, though only from within the namespace where that import exists.

I'll start with a new project and add a dependency that is clearly not used anywhere in the default project, in this case the Riemann client for monitoring stuff (it's a great program check it out)

lein new hello

and add the dep

(defproject hello "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :aot :all
  :main hello.core
  :profiles {:uberjar {:aot :all}}
  :dependencies [[org.clojure/clojure "1.6.0"]
                 [com.aphyr/riemann-java-client "0.3.1"]])

then first thing, I'll look at that class by it's full name without any import

hello.core> com.aphyr.riemann.client.RiemannClient
com.aphyr.riemann.client.RiemannClient

then try the short name:

hello.core> RiemannClient
CompilerException java.lang.RuntimeException: Unable to resolve symbol: RiemannClient in this context, compiling:(/tmp/form-init3055907211270530213.clj:1:5933) 

which does not work:

hello.core> (import '[com.aphyr.riemann.client RiemannClient])
com.aphyr.riemann.client.RiemannClient

Then if we add the import and try again:

hello.core> RiemannClient
com.aphyr.riemann.client.RiemannClient

the name resolves from within the user namespace.
If I change namespaces:

hello.core> (in-ns 'foo)

and then look at the class again:

foo> RiemannClient
CompilerException java.lang.RuntimeException: Unable to resolve symbol: RiemannClient in this context, compiling:(/tmp/form-init3055907211270530213.clj:1:5933) 
foo> com.aphyr.riemann.client.RiemannClient
com.aphyr.riemann.client.RiemannClient

we can see that imports are per-namespace, though classes on the classpath are available everywhere.