Clojure

名前空間に既にロードされているクラスと、importしたいクラスの名前衝突を回避する

More than 1 year has passed since last update.

Compilerという名のクラスをJavaからインポートしたいとします。

(ns leiningen.zinc
  "Typesafe scala zinc incremental compiler plugin for leiningen."
  (:require [leiningen.classpath :as classpath]
            [leiningen.core.main :as main])
  (:import  (com.typesafe.zinc Compiler Setup)))

一見問題なさそうですが、nsマクロを評価すると、下記のようなエラーが出力されます。

Caused by: java.lang.IllegalStateException: Compiler already refers to: class clojure.lang.Compiler in namespace: leiningen.zinc

com.type.safe.zinc.Compilerが、暗示的に参照されているclojure.lang.Compilerと衝突しているようです。

nsマクロは、デフォルトで、clojure.core内の全ての関数、java.lang内の全てのクラス、そしてclojure.lang.Compilerをマップに加えてから生成されます。

まず思いつくのは、require:asオプションで別名をつけてやることで名前衝突を回避できるかどうかですが、この場合、clojure.lang.Compilerは既にロードされているので、ライブラリをロードするrequireを用いることができませんし、asは名前空間に別名をつけるもので、クラスは対象でありません。

では、import側でエイリアスをつけることは可能でしょうか?docstringとソースコードを見る限り、サポートされていないようです。

更に調べると、referを用いると、名前空間内のパブリック変数を参照し、かつ:exclude:only:renameのフィルターをかけることができるようです。

まずは、名前空間のマップにバインドされている内容の一覧を取得してみましょう。

leiningen.zinc=> (ns-map 'leiningen.zinc)
{primitives-classnames #'clojure.core/primitives-classnames, +' #'clojure.core/+', Enum java.lang.Enum,
...
number? #'clojure.core/number?, Compiler clojure.lang.Compiler,...

名前空間のマップはVarと、Classの両方を含んでいます。referはVarにしか適用できませんので、referのフィルタをクラスであるclojure.lang.Compilerに適用させることはできません。

そこで、ns-unmapを用いて、名前空間マップからCompilerのエントリを取り除き、それから改めて、importnsマクロのキーワードとしてではなく、importマクロを直接呼び出すことで、Compilerエントリを追加しています。

(ns leiningen.zinc
  "Typesafe scala zinc incremental compiler plugin for leiningen."
  (:require [leiningen.classpath :as classpath]
            [leiningen.core.main :as main]))
(ns-unmap *ns* 'Compiler)
(import  (com.typesafe.zinc Compiler Setup))
...

このような操作が煩雑であれば、importをせず、com.typesafe.zinc.Compilerをフルパスで指定して使えば良いでしょう。