2009-10-28 12 views
11

Clojureに引数を検証したい数値関数のグループがあります。正の整数、パーセンテージ、数字のシーケンス、非ゼロの数字のシーケンスなど、関数によって期待される多くの種類の引数があります。Clojureで引数を検証するために関数またはマクロを使うべきですか?

  1. が右 関数に検証コードを書く:私は、ことによって、任意の個々の関数に引数を検証することができます。
  2. 汎用関数 を作成し、引数と期待される型を に渡します。
  3. 一般的な目的のマクロを書くと、 は引数と の予想される型を渡します。
  4. その他私は考えていません。

は、#3の素敵な例です。 (test-variablesマクロを探してください)

私の直感は、実行時に評価するのではなく、評価を制御し、コンパイル時の計算を行う可能性があるので、マクロが適切であるということです。しかし、私はそれを必要としているように私が書いているコードのユースケースに遭遇していない。私はそのようなマクロを書く価値があるのだろうかと思っています。

提案がありますか?

答えて

13

Clojureは、すでにfnの条件の前と後に対応しています(文書化されていない可能性があります)。

user> (defn divide [x y] 
     {:pre [(not= y 0)]} 
     (/ x y)) 
user> (divide 1 0) 
Assert failed: (not= y 0) 
    [Thrown class java.lang.Exception] 

醜いですが。

私はたぶんマクロを書いて、どのテストが簡潔な方法で失敗したのかを報告することができます(引用してテストをそのまま印刷します)。リンクしたCLコードは、その巨大なcase文でかなり厄介に見えます。私の考えでは、ここではマルチメソッドが良いでしょう。あなたはこのようなものをかなり簡単に一緒に投げることができます。

(defmacro assert* [val test] 
    `(let [result# ~test]    ;; SO`s syntax-highlighting is terrible 
    (when (not result#) 
     (throw (Exception. 
       (str "Test failed: " (quote ~test) 
        " for " (quote ~val) " = " ~val)))))) 

(defmulti validate* (fn [val test] test)) 

(defmethod validate* :non-zero [x _] 
    (assert* x (not= x 0))) 

(defmethod validate* :even [x _] 
    (assert* x (even? x))) 

(defn validate [& tests] 
    (doseq [test tests] (apply validate* test))) 

(defn divide [x y] 
    (validate [y :non-zero] [x :even]) 
    (/ x y)) 

その後:あなたは、自動的にこのように、ブロック内で定義された任意の関数にテストを追加する言語を変更したい場合は、マクロが必要になります

user> (divide 1 0) 
; Evaluation aborted. 
; Test failed: (not= x 0) for x = 0 
; [Thrown class java.lang.Exception] 

user> (divide 5 1) 
; Evaluation aborted. 
; Test failed: (even? x) for x = 5 
; [Thrown class java.lang.Exception] 

user> (divide 6 2) 
3 
+0

ニース;それを知らなかった – pmf

+0

別の素晴らしい答えをありがとう。これは私がやりたいこととほぼ同じです。 また、マクロがより良い解決策である理由の1つを示しています。関数が2つ以上の同じ特性、たとえば2つの正の整数を必要とする関数の場合、問題の引数を明確にする方法について考えてみましょう。マクロルートを使用すると、値だけでなく問題の引数の名前をエラーメッセージに入れることができます。私はあなたが関数でそれを行うことはできないと思います。 – clartaq

+1

前提条件と事後条件がここに記載されています。http://clojure.org/special_forms条件マップを検索します。 – Chouser

3

ほんの少しの考えです。

私は、検証の複雑さと数、および機能の性質によって決まります。

非常に複雑な検証を行っている場合は、バリデータを関数から外す必要があります。その理由は、より単純なものを使用してより複雑なものを構築できるということです。

たとえば、以下のように記述します:

  1. 値がゼロより大きいことを確認するバリデータ
  2. 、リストが空でないことを確認するバリデータ、
  3. 使用1と2をします値が0より大きい値の空でないリストであることを確認してください。

(例えばあなたはすべてがゼロ以外の整数を必要とする50個の機能を持っている)、あなただけの簡単な検証の膨大な量をやっているし、あなたの問題が冗長である場合、マクロはおそらくより理にかなっています。

もう1つのことは、関数評価がClojureが熱心だということです。関数が失敗することがわかっている場合、または他のパラメータの値に基づいていくつかのパラメータが必要でない場合、一部のパラメータを評価しないことによって、パフォーマンスが向上する場合があります。例えば。すべての?述部はコレクション内のすべての値を評価する必要はありません。

最後に、「他の人にはあなたは考えていない」と言います。 Clojureは、ディスパッチ機能に基づいて汎用ディスパッチをサポートしています。その関数は適切なコードにディスパッチするか、任意の数の要因に基づいてエラーメッセージを送出することができます。

1

場合:

(with-function-validators [test1 test2 test4] 
    (defn fun1 [arg1 arg2] 
     (do-stuff)) 
    (defn fun2 [arg1 arg2] 
     (do-stuff)) 
    (defn fun3 [arg1 arg2] 
     (do-stuff))) 
関連する問題