ラベル Assertion の投稿を表示しています。 すべての投稿を表示
ラベル Assertion の投稿を表示しています。 すべての投稿を表示

2012年9月23日日曜日

ClojureでDesign By Contract

Clojureでは、関数を定義する際に事前条件と事後条件を定義できます。
この機能を使えば、Design By Contract(契約による設計)を容易に実践できます。

事前条件を定義する
さっそく実験してみます。
「パラメータが文字列であること」という事前条件をもつ関数aを定義しました。

user=> (defn a [x] {:pre [(instance? String x)]} x)
#'user/a
user=> (a 1)
AssertionError Assert failed: (instance? String x) user/a (NO_SOURCE_FILE:1)
user=> (a "123")
"123"
view raw assert.clj hosted with ❤ by GitHub
パラメータとして1を渡したときにAssertionErrorが発生しました。

Assertionを無効にする
Assertionを無効にするには、*assert*へfalseをセットします。
この値はコンパイル時に参照されるため、REPLの場合はdefnを実行する前に設定しておく必要があります。
*assert*へfalseを設定してから、上と同じ関数を定義しました。

user=> (set! *assert* false)
false
user=> (defn a [x] {:pre [(instance? String x)]} x)
#'user/a
user=> (a 1)
1
user=> (a "123")
"123"
パラメータとして1を渡しても例外が発生しなくなりました。

例をもう少し
最後に、もう少し実践的な例として日付の比較をする関数を上げておきます。
この関数はCalendar.beforeを呼ぶため、パラメータはCalendarクラスのインスタンスでなければいけません。

user=> (import java.util.Calendar)
java.util.Calendar
;; 関数定義
user=> (defn before? [base comp]
#_=> {:pre [(instance? Calendar base)
#_=> (instance? Calendar comp)]
#_=> :post [(instance? Boolean %)]}
#_=> (.before base comp))
#'user/before?
;; 動作確認
user=> (def cal1 (Calendar/getInstance))
#'user/cal1
user=> (def cal2 (Calendar/getInstance))
#'user/cal2
user=> (before? cal1 cal2)
true
user=> (before? cal2 cal1)
false
;; Calendar以外を渡してみる
user=> (before? 1 2)
AssertionError Assert failed: (instance? Calendar base) user/before? (NO_SOURCE_FILE:1)
view raw before.clj hosted with ❤ by GitHub
事前条件と事後条件をシンプルに定義できるのが素敵だと思いました。