メタデータのこれまでとこれから

最初に謝らなければならないことがあります。ごめんなさい。私が間違っていました。


耳当てつきのvarはダイナミックスコープを持つと自動的にみなされない - OGINO Masanori@はてなで、:dynamicメタデータの値をtrueにするために、このように書きました。

(def ^:dynamic *fred*)

^:dynamicという書き方は1.3.0から追加されましたが、これを1.2のREPLに入力しても例外は発生しません。

(clojure-version) ;=> "1.2.1"
(def ^:dynamic *fred*) ;=> #'user/*fred*

私はこれを見て「1.2でも動くんだな*1」と思い、そう話しました。

私が間違っていましたコンパイラに渡すと「Unable to resolve classname: :dynamic」という例外が発生します。

以下にその理由と対策を示します。

これまでのメタデータ

1.0では、^fooは(meta foo)に展開され、varにメタデータを付加する際には#^を使いました。

;; (meta #'str)
^#'str

(defn #^{:tag String} long-hello
  [#^{:tag String} your-name]
  (str "hello, " your-name))

この:tagメタデータは型を指定するもので、コンパイラにリフレクションを使わないコードを生成させるために付加します。
この:tagメタデータには#^Klassというショートカットがあり、#^{:tag Klass}に展開されます。

(defn #^String short-hello
  [#^String your-name]
  (str "hello, " your-name))

しかし、1.1で^fooは非推奨となり、1.2で削除されました。用途がなくなった^は#^の新しい書き方として再定義され、1.2で#^は非推奨となりました。

(defn ^{:tag String} long-hello
  [^{:tag String} your-name]
  (str "hello, " your-name))

(defn ^String short-hello
  [^String your-name]
  (str "hello, " your-name))

これが今回のコンパイルエラーの原因です。:dynamicという名前のクラスを探して失敗していたのです。

これからのメタデータ

そして、1.3では:dynamic以外にもtrueかどうかをコンパイラが調べるメタデータが追加されたので、^:keywordが^{:keyword true}に展開されるようになりました。

最後に、耳当てつきのvarはダイナミックスコープを持つと自動的にみなされない - OGINO Masanori@はてなの最後のコードを1.2と1.3の両方で動かす方法を示します。

一つは、^{:dynamic true}と書くことです。1.2のコンパイラは:dynamicを考慮しませんが、害もありません。

(def ^{:dynamic true} *fred*)

1.3へ完全に移行してから^:dynamicに書き直してもいいですし、書き直さなくても特に問題はありません。

もう一つは、1.2のコンパイラが:tagメタデータを最初の一つだけ読み込む点を利用して、先に型を指定することです。

;; *fred*は文字列
(def ^String ^:dynamic *fred*)

しかし、少々トリッキーなので、私はあまり好みません。

おわりに

私の検証不足で人に誤りを伝えました。申し訳ありません。

*1:以下に登場する:tagメタデータに:dynamicという値が入っていることには気付いていましたが、クラス以外が入っている場合は無視されるものと勘違いしていました。