IK.AM

@making's tech note


Clojureの関数にcondition-mapでPre・Postチェック

🗃 {Programming/Lisp/Clojure}
🗓 Updated at 2010-03-24T17:08:44Z  🗓 Created at 2010-03-24T17:08:44Z   🌎 English Page

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版)を読んで、おおっと思ったことのまとめです。


✒️️ Edit  ⏰ History  🗑 Delete