2009-10-28 12 views
11

Mam grupę funkcji numerycznych w Clojure, które chcę zweryfikować argumenty dla. Istnieje wiele typów argumentów oczekiwanych przez funkcje, takie jak dodatnie liczby całkowite, procenty, sekwencje liczb, sekwencje liczb niezerowych itd. Potrafię zweryfikować argumenty każdej pojedynczej funkcji przez:Czy powinienem używać funkcji lub makra do sprawdzania poprawności argumentów w Clojure?

  1. Zapisywanie kodu walidacyjnego bezpośrednio w funkcji .
  2. Zapisywanie ogólnej funkcji celu , przekazywanie jej argumentów i spodziewanych typów .
  3. Tworzenie makra ogólnego przeznaczenia, przekazywanie argumentów i oczekiwanych typów .
  4. Inni, o których nie pomyślałem.

Niektóre Lisp code przez Larry Hunter jest dobrym przykładem # 3. (Poszukaj makro test-variables.)

Mam intuicję, że makro jest bardziej odpowiednie ze względu na kontrolę nad oceną i możliwość wykonywania obliczeń w czasie kompilacji, zamiast wykonywać to wszystko w czasie wykonywania. Ale nie natknąłem się na przypadek użycia kodu, który piszę, który zdaje się go wymagać. Zastanawiam się, czy warto napisać takie makro.

Wszelkie sugestie?

Odpowiedz

13

Clojure ma już (nieudokumentowane, może podlegające zmianie) wsparcie dla warunków wstępnych i końcowych na fn s.

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] 

Trochę brzydki.

Najprawdopodobniej napiszę makro, żeby móc raportować, które testy zawiodły w zwięzły sposób (zacytuj i wydrukuj test dosłownie). Kod CL, z którym się łączyłeś, wygląda dość nieprzyjemnie z tym ogromnym opisem sprawy. W mojej opinii multimetody byłyby tutaj lepsze. Możesz rzucić coś takiego razem dość łatwo siebie.

(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)) 

Następnie:

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

Nice; nie wiedziałem, że – pmf

+0

Dzięki za kolejną wspaniałą odpowiedź. Dokładnie to właśnie chciałem zrobić. To również ilustruje jeden z powodów, dla których makro jest lepszym rozwiązaniem. Zastanawiając się nad ujednoznacznieniem argumentu problemu, jeśli funkcja wymaga dwóch lub więcej z tymi samymi cechami, powiedzmy dwie liczby całkowite dodatnie, trasa makra pozwala umieścić nazwę argumentu problemu w komunikacie o błędzie, a nie tylko wartość. Nie sądzę, żebyś mógł to zrobić za pomocą funkcji. – clartaq

+1

Warunki wstępne i warunki postojowe są udokumentowane tutaj http://clojure.org/special_forms Wyszukaj mapę warunków. – Chouser

3

Tylko kilka myśli.

Mam wrażenie, że zależy to od złożoności i liczby walidacji oraz charakteru funkcji.

Jeśli wykonujesz bardzo złożone sprawdzenia, powinieneś przerwać działanie walidatorów. Rozumowanie jest takie, że można użyć prostszych do zbudowania bardziej złożonych.

Na przykład, piszesz:

  1. weryfikator aby upewnić się lista nie jest pusta,
  2. weryfikator do upewnij się, że wartość jest większa od zera,
  3. użycie 1 i 2 do upewnij się, że wartość jest niepustą listą wartości większą od zera.

Jeśli po prostu wykonujesz ogromną liczbę prostych sprawdzeń, a twoim problemem jest gadatliwość (np. Masz 50 funkcji, które wszystkie wymagają niezerowych liczb całkowitych), to makro prawdopodobnie ma więcej sensu.

Inną rzeczą, którą należy wziąć pod uwagę, jest to, że funkcja oceny jest gotowa. W niektórych przypadkach możesz uzyskać zwiększenie wydajności, nie oceniając niektórych parametrów, jeśli wiesz, że funkcja zakończy się niepowodzeniem lub jeśli niektóre parametry nie są potrzebne na podstawie wartości innych parametrów. Na przykład. co? predykat nie musi oceniać każdej wartości w kolekcji.

Wreszcie, aby zaadresować "innych, o których nie myślałeś". Clojure obsługuje ogólną wysyłkę pbased na funcie wysyłki. Ta funkcja może wywoływać odpowiedni kod lub komunikaty o błędach oparte na dowolnej liczbie czynników.

1

Przypadek gdzie trzeba makro byłoby gdybyś chciał zmieniać języka automatycznie dodać testy do dowolnej funkcji zdefiniowanej w bloku, jak poniżej:

(with-function-validators [test1 test2 test4] 
    (defn fun1 [arg1 arg2] 
     (do-stuff)) 
    (defn fun2 [arg1 arg2] 
     (do-stuff)) 
    (defn fun3 [arg1 arg2] 
     (do-stuff))) 
Powiązane problemy