CLOVER🍀

That was when it all began.

addedメタデータから、varのClojureに追加されたバージョンを確認する

Clojure単独ネタ。Namespaceに定義されたvarをスキャンして、そのaddedメタデータからClojureに追加されたバージョンとvarの一覧みたいなものが作れないかなぁということを、ちょっと試してみたくなりまして。

結論からいくと、こんな感じのソースでできました。
find_added_sources.clj

(import '(clojure.lang Var))

(defn filter-vars [target-namespaces pred]
  (filter pred
          (for [n target-namespaces
                mv (vals (. n getMappings))
                :when (instance? Var mv)]
            mv)))

;; 全Namespaceから検索
(def target-namespaces (all-ns))
;; 指定のNamespaceのシーケンスから検索
;;(def target-namespaces [(find-ns 'clojure.core)
;;                        (find-ns 'clojure.string)])

(doseq [n (filter-vars target-namespaces
                       #(and (not (nil? ((meta %) :added)))
                             (>= (compare ((meta %) :added) "1.4") 0)))]
  (println (str n " => added[" ((meta n) :added) "]")))

これを実行すると、こんな結果が得られます。

$ clj find_added_sources.clj 
#'clojure.core/cond->> => added[1.5]
#'clojure.core/send-via => added[1.5]
#'clojure.core/set-agent-send-executor! => added[1.5]
#'clojure.core/mapv => added[1.4]
#'clojure.core/*data-readers* => added[1.4]
#'clojure.core/filterv => added[1.4]
#'clojure.core/some-> => added[1.5]
#'clojure.core/reduced? => added[1.5]
#'clojure.core/default-data-readers => added[1.4]
#'clojure.core/as-> => added[1.5]
#'clojure.core/ex-info => added[1.4]
#'clojure.core/reduced => added[1.5]
#'clojure.core/*default-data-reader-fn* => added[1.5]
#'clojure.core/reduce-kv => added[1.4]
#'clojure.core/*compiler-options* => added[1.4]
#'clojure.core/ex-data => added[1.4]
#'clojure.core/set-agent-send-off-executor! => added[1.5]
#'clojure.core/some->> => added[1.5]
#'clojure.core/cond-> => added[1.5]
#'clojure.core/cond->> => added[1.5]

〜省略〜

ここでは、addedメタデータに「1.4」以上の値が書かれているものを対象にしています。

説明ですが、ClojureのNamespaceの実体はclojure.lang.Namespaceというクラスに定義されていて、Namespace内にあるものはmappingsというフィールド(Mapです)に格納されています。

そして、varはclojure.lang.Varというクラスとなっています。

ですので、Namespaceのmappings内からVarのみ抽出しました。

          (for [n target-namespaces
                mv (vals (. n getMappings))
                :when (instance? Var mv)]
            mv)))

あとはそれを、任意の述語でフィルタリングしています。

今回のお題から、以下のような述語としました。

                       #(and (not (nil? ((meta %) :added)))
                             (>= (compare ((meta %) :added) "1.4") 0)))]

meta関数を使い、varからメタデータを抽出し、addedがあり、かつ指定の文字列より大きい値であればtrueとなるようにしています。

最後に、それを表示しているだけです。

(doseq [n (filter-vars target-namespaces
                       #(and (not (nil? ((meta %) :added)))
                             (>= (compare ((meta %) :added) "1.4") 0)))]
  (println (str n " => added[" ((meta n) :added) "]")))

なお、Namespaceは今回全てということでall-ns関数を使いました。all-ns関数を使用すると、全Namespaceがシーケンスとして返却されます。

;; 全Namespaceから検索
(def target-namespaces (all-ns))

任意のNamespaceに対して行う場合は、find-ns関数を使うとよいでしょう。

;; 指定のNamespaceのシーケンスから検索
(def target-namespaces [(find-ns 'clojure.core)
                        (find-ns 'clojure.string)])

ただ、all-ns関数を使った場合でもReducersみたいなrequireが必要なものについては、明示的に先にrequireしておかないと対象に含まれないので、その点はご注意を。

また、そもそもaddedメタデータが付与されていなければ、単に無視されるだけになります。