Clojure.specは、Clojureの新たなコントラクトシステムである

Clojureに新しいコアライブラリ、clojure.specが加わった。 Clojureの作者、Rich Hickeyによると、 このライブラリは、データおよび関数の仕様記述とテスティングに関する、標準的で統合されたシステムの提供を目的としている。 このライブラリは、Clojureコードの自動的な検証だけでなく、生成的テスティング、エラー報告、デストラクチャリング、その他様々なタスクに利用することができる。

clojure.specは、仕様の記法を改善する。 仕様記述には、述語(integer?#(< 42 % 66)など)の論理結合が使用される。 clojure.specは、spec/andspec/orといった、論理演算子を提供する。 Hickey氏によれば、clojure.specは、コントラクトシステム(contract systems)として知られる成果を元に開発されている。 コントラクトシステムは、RDFRacket、その他にみられる。

たとえば、あるmapは、:req:optを引数として、keysを呼ぶものとして記述できる。

(spec/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z])

注目すべきは、このmapの仕様が、与えられたキーワードに対応する値の種類が何かを指定する方法を提供しないことだ。 直接指定する代わりに、clojure.specは、キーワード自身に紐付く名前空間キーワードに関連付けられた値の仕様を推奨- 時には強制- する。

(spec/def ::x integer?)
(spec/def ::y integer?)
(spec/def ::z integer?)

このような値の仕様記述は、同一のキーワードを使用するどのようなmapに対しても適用される。

clojure.specにおいては、シーケンスは、正規表現として仕様記述できる。 述語がどのようにシーケンスと一致するかを記述した正規表現として記述できるのだ。 関数は、3つの分離した仕様として記述できる。

  • 単純なリストとして表現される"引数"。
  • 単一の値として表現される"戻り値"。
  • 引数と戻り値の"関係"。関係の記述は必須ではない。たとえば、「入力マップの全てのキーが、戻りのマップに表れていること」を記述する場合、必要となる。

clojure.specを使用したClojureの仕様記述に関する詳細は、ここを参照してほしい。

仕様を一度書けば、値に対してconformを適用することで、検証できるようになる。 もし、conform:clojure.spec/invalidを返した場合は、explainにより、原因を探ることができる。 instrumentを使うと、関数を一つにまとめあげる。これにより、関数の持つ3つの仕様をテストできる。 テストのために、run-testsを利用できる。 これは、1つの名前空間に対して生成的テストスイートを実行する。 あるいは、genにより、1つの仕様のためのジェネレータを構築できる。 このジェネレータは、test.check互換である。

clojure.specは、Clojureに仕様記述を取り込む初の試み、というわけではない。 これまでにも、開発者たちは、SchemaHerbertを使用してきた。 Clojureの動作を保証するための別のアプローチとしては、clojure.typedもある。 clojure.typedは、コンパイル時検証のコンセプトを実現するための、漸進的型付け(gradual typing)ライブラリである。

clojure.specを利用可能なClojureのバージョンは、1.9.0/alpha1以降である。 project.cljに次の記述を追加することにより、利用できるようになる。[org.clojure/clojure "1.9.0-alpha1"]