Clojureの関数の形式は実は
(defn function-name doc-string? attr-map? [parameter-list] conditions-map? (expressions))
です。 doc-string?
はドキュメント、attr-map?
はメタ情報です。引数の後にあるcondition-map?
はなんぞやというと、関数の入出力の条件を設定できるmapです。(Clojure 1.1.0からかな?)
condition-map?
の形式は
{:pre 入力値をチェックする条件S式のシーケンス, :post 出力値をチェックする条件S式のシーケンス}
です。例えば、引数が全て正でかつ、関数の結果が10より大きいという条件を付けたい場合、
(defn hoge [x y] {:pre [(> x 0) (> y 0)] :post [(> % 10)]} (+ x y))
という風になります。%には返り値が入ります。
条件を満たさない場合は、java.lang.AssertionError
が発生します。
(ちなみにleiningen1.0.1のreplのバージョンであるClojure 1.1.0-master-SNAPSHOTではjava.lang.Exception
でした。。。これはExceptionかErrorかという大きな違いであり、明文化が待たれます)
実行してみると、
user=> (hoge 5 6) 11 user=> (hoge 0 11) java.lang.AssertionError: Assert failed: (> x 0) (NO_SOURCE_FILE:0) user=> (hoge 11 0) java.lang.AssertionError: Assert failed: (> y 0) (NO_SOURCE_FILE:0) user=> (hoge 5 5) java.lang.AssertionError: Assert failed: (> % 10) (NO_SOURCE_FILE:0)
となります。
モジュラリティを高める
このままだと常にチェックが走りますが、単純に関数を使う場合を分けたいことがあると思います。次のように書くと、関数の機能とチェック機能を分離させることができ、モジュラリティを高めることができます。
user=> (defn hoge [x y] (+ x y)) #'user/hoge user=> (hoge 5 5) 10 user=> (defn with-condition [f x y] {:pre [(> x 0) (> y 0)], :post [(> % 10)]} (f x y)) #'user/with-condition user=> (with-condition hoge 5 5) java.lang.AssertionError: Assert failed: (> % 10) (NO_SOURCE_FILE:0)
partial
で部分適用しておけば、元の関数を同じものを定義できますね。
user=> (def always-condition (partial with-condition hoge)) #'user/always-condition user=> (always-condition 5 5) java.lang.AssertionError: Assert failed: (> % 10) (NO_SOURCE_FILE:0)
エンタープライズを乗っとるには堅牢性が必要になると思います。condition-map?
を上手に使うことは乗っ取りの第一歩ですね!
ちなみにここ数回のClojureネタはClojure in Action(MEAP版)を読んで、おおっと思ったことのまとめです。