(ns foo.core)
(defmacro add
[a b]
`(+ ~a ~b))
ClojureScript の ns フォームは、ほとんどの場合、特に最近 ClojureScript に追加された機能のおかげで、非常にシンプルで Clojure と似たものになります。
しかし、ns フォームの根底には豊富なオプション群があり、実際にそれらの使用に遭遇することがあります。このガイドでは、これらのオプションを詳しく解説し、明確にすることを目的としています。
ClojureScript では、マクロは Clojure とは少し異なる方法で処理されます。特に、ns フォームは、:require-macros をサポートしており、ここで説明する簡略化された糖衣構文と暗黙的なロード動作があります。
例として、src/foo/core.clj に以下があることを前提としましょう。
(ns foo.core)
(defmacro add
[a b]
`(+ ~a ~b))
ClojureScript のソースでこのマクロを使用するには、:require-macros 仕様を使用できます。
(ns bar.core
(:require-macros [foo.core]))
(foo.core/add 2 3)
さて、:require-macros は :require とよく似た動作をするように設計されています。例えば、add シンボルを名前空間に直接参照することができます。
(ns bar.core
(:require-macros [foo.core :refer [add]]))
(add 2 3)
上記の代替として(あまり見られませんが、完全を期すために説明します)、
:use-macrosがあります。ClojureScript では、:require/:referと:use/:onlyは、本質的に互いに双対的な形式です。したがって、上記のnsフォームは、(ns bar.core (:use-macros [foo.core :only [add]]))と記述することもできました。
名前空間エイリアス foo を設定することもできます。
(ns bar.core
(:require-macros [foo.core :as foo]))
(foo/add 2 3)
上記の例はすべて、:require-macros プリミティブを使用しています。
しかし、多くの場合、ランタイムコード(関数やその他の def)とマクロの両方を、すべて同じ名前空間から提供するライブラリからのコードを使用することになります。
したがって、例を続けると、src/foo/core.cljs ファイルに以下がある場合を考えましょう。
(ns foo.core)
(defn subtract
[a b]
(- a b))
ここで、add と subtract の両方を使用したい場合は、次のように記述するかもしれません。
(ns bar.core
(:require-macros [foo.core :refer [add]])
(:require [foo.core :refer [subtract]]))
(add 2 3)
(subtract 7 4)
しかし、ns フォームには、代わりに記述できる糖衣構文、:refer-macros があります。
(ns bar.core
(:require [foo.core :refer [subtract] :refer-macros [add]]))
(add 2 3)
(subtract 7 4)
上記の :refer-macros は単なる糖衣構文であり、コンパイラによって前の形式にアルゴリズム的に脱糖化されます。
同様に、ランタイム名前空間と同じ仕様を使用してマクロ名前空間を require すべきであることを示す :include-macros 糖衣構文があります。例えば、これは機能します。
(ns bar.core
(:require [foo.core :as foo :include-macros true]))
(foo/add 2 3)
(foo/subtract 7 4)
上記は、より冗長なプリミティブ形式に脱糖化されます。
(ns bar.core
(:require-macros [foo.core :as foo])
(:require [foo.core :as foo]))
(foo/add 2 3)
(foo/subtract 7 4)
require されているランタイム名前空間が、内部的に独自の(同じ名前の)マクロ名前空間を require する場合、:include-macros 糖衣構文が暗黙的に無料で提供されます。これはライブラリを開発している場合に推奨され、ユーザーは require フォームを簡略化できます。
これを説明するために、src/foo/core.cljs ファイルが代わりに次のようになっているとしましょう。
(ns foo.core
(:require-macros foo.core))
(defn subtract
[a b]
(- a b))
これで、次のようなものを消費することができます。
(ns bar.core
(:require [foo.core :as foo]))
(foo/add 2 3)
(foo/subtract 7 4)
この見栄えの良い簡略化はどうでしょうか?
(ns bar.core
(:require [foo.core :refer [add subtract]))
(add 2 3)
(subtract 7 4)
この場合、add がマクロであり、subtract が関数であるという事実はコンパイラによって自動的に処理されるため、ns フォームが Clojure で記述するのと同じように見え、変数の一様な参照が可能になります。
REPL で上記のトピックをすばやく参照する必要がある場合は、ns 特殊フォームの docstring が役立ちます。糖衣構文はインラインマクロ仕様と呼ばれ、暗黙的な糖衣構文は暗黙的なマクロロードと呼ばれます。脱糖化のかなり包括的な例が docstring に含まれています。困ったときは、(doc ns) が役に立ちます。
require および require-macros マクロrequire と require-macros を使用して、REPL にコードを動的にロードできます。興味深いのは、上記で説明した機能がこれらのマクロでも機能することです。
これは実装の詳細ですが、これがどのように達成されるかを確認するのに役立ちます。次を発行すると
(require-macros '[foo.core :as foo :refer [add]])
REPL で、これは内部的に次のような ns フォームに変換されます。
(ns cljs.user
(:require-macros [foo.core :as foo :refer [add]]))
そして、重要なことに、require を使用すると、同様の ns フォームが使用され、上記で説明したすべての脱糖化と推論動作の対象になります。
clojure 名前空間エイリアスclojure.string や clojure.set など、一部の名前空間は、それらの名前空間の最初のセグメントが clojure であっても、ClojureScript で使用できます。しかし、cljs.pprint、cljs.test、そして現在は cljs.spec などは、cljs の下にあります。
なぜ違いがあるのでしょうか?理想的には、違いはないはずです。しかし、例えば、ClojureScript で使用するための clojure.pprint の移植を見ると、マクロ名前空間が含まれています。ここに問題があります。JVM ClojureScript コンパイラは実行に Clojure を使用するため、ポートが cljs.pprint に移動されなければ、名前空間の衝突が発生します。つまり、clojure.pprint 名前空間は使用済みでした。
この結果、ClojureScript を記述するときに、一部の名前空間に cljs.* を使用することを覚えておく必要があります。そして、ポータブルなコードを記述している場合は、リーダー条件を使用する必要があります。
ns フォームには、比較的最近追加された簡略化機能があり、利用できます。cljs.* 名前空間にマップできる、存在しない clojure.* 名前空間の場合、名前空間の最初のセグメントで cljs の代わりに clojure を使用できます。
簡単な例
(ns foo.core
(:require [clojure.test]))
の代わりに、次のように使用できます。
(ns foo.core
(:require [cljs.test]))
こうすると、ClojureScript コンパイラは最初に clojure.test 名前空間をロードできるかどうかを確認します。存在しないため、cljs.test のロードにフォールバックします。
同時に、clojure.test から cljs.test へのエイリアスが、次のように記述したかのように設定されます。
(ns foo.core
(:require [cljs.test :as clojure.test]))
これは、clojure.test/test-var のように、シンボルを修飾するコードを持つことができるため重要です。
このエイリアス化と、:refer 仕様でマクロ変数を推論する機能(上記の「暗黙的な参照」を参照)により、次のコードは ClojureScript で正常に機能します。
(ns foo.core-test
(:require [clojure.test :as test :refer [deftest is]]))
(deftest foo-test
(is (= 3 4)))
(test/test-var #'foo-test)
さらに重要なのは、これは Clojure で記述するのと同じコードであるということです。リーダー条件は必要ありません!
もちろん、これは require ClojureScript マクロでも機能します。例えば、次のように記述できます。
(require '[clojure.spec :as s])
その後、(s/def ::even? (s/and number? even?)) は正常に機能します。この理由は、require マクロが ns 特殊フォームに関して実装されているためです。
これらの詳細な例が、ns の脱糖化、推論、およびエイリアス化の仕組みを明確にするのに役立つことを願っています。全体的な意図は、ClojureScript の ns フォームの使用を簡略化することですが、これらの追加機能の仕組みを解明することで、実際に何が起こっているのかを知りたい、または知る必要がある場合に、より良い理解につながります。
これらの機能をうまく活用することで、ClojureScript と Clojure の ns フォームの違いを大幅に軽減できるはずです。
原著者: Mike Fikes