CLOVER🍀

That was when it all began.

Clojureでキャストを使う

Clojureでキャストを行うためには、clojure.core/cast関数を使用します。

cast
http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/cast

http://stackoverflow.com/questions/3652675/how-can-i-cast-a-java-class-in-clojure

使い方は、

(cast [型] [キャスト対象のオブジェクト])

で、

(cast String "hello")

みたいな感じで使います。上記の例だと、全く意味ないですけど。

これを使った、適当なお題はないかなぁと思って、以前Clojureを使っていてjava.util.ExecutorService#submitをClojureの関数を渡そうとしてちょっと失敗したことを思い出したので、これに適用してみることにしました。

ちなみに、オチは微妙です…。

とりあえずimport文を書いておきます。

(import '(java.util.concurrent Callable Future Executors))

Clojureの関数は、RunnableでもCallableでもあるので、こういうコードを書くと

(let [es (Executors/newSingleThreadExecutor)]
  (try
    (let [func (fn [] "Function is Runnable And Callable.")
          f (.submit es func)])
    (finally (.shutdown es))))

呼び出し対象が複数マッチするため、コンパイルに失敗します。

Exception in thread "main" java.lang.IllegalArgumentException: More than one matching method found: submit, compiling:...

ここで、cast関数を使ってCallableとして渡してみます。

(let [es (Executors/newSingleThreadExecutor)]
  (try
    (let [func (fn [] "Function is Runnable And Callable.")
          f (.submit es (cast Callable func))]
      (assert (nil? (.get f))))
    (finally (.shutdown es))))

すると、実行はできましたがassertで検証しているところでお気付きかもしれませんが、結果がnil…。

ExecutorService#submitは呼び出せているものの、Callableとしては期待の呼び出し結果になりませんでした…。

ちなみに、これは普通に動きます。

(assert (.call (fn [] "Function is Runnable And Callable.")) "Function is Runnable And Callable.")

仕方がないので、ここは変数に型ヒントを。

(let [es (Executors/newSingleThreadExecutor)]
  (try
    (let [^Callable func (fn [] "Function is Runnable And Callable.")
          f (.submit es func)]
      (assert (= (.get f) "Function is Runnable And Callable.")))
    (finally (.shutdown es))))

これなら、期待の結果が得られました。型ヒントはコンパイル時に使われるものですが、cast関数は実行時ですからねぇ…。この差なのかなぁ?

オーバーロードされたメソッド呼び出しで困った時は、cast関数でダメなら1度変数に落として型ヒントを与えて解決するとよいのかもしれません。