2013-04-25 12 views
6

Kiedy używać if-niech jakClojure: Dlaczego pozwala się tylko na 2 formularze w wektorze wiążącym?

(if-let [a 2 b nil] (+ a b)) 

dostaję IllegalArgumentException:

clojure.core/if-let requires exactly 2 forms in binding vector... 

Podobny do kiedy-let ...

To jest nie to, co by się spodziewać. If-let może wypróbować wszystkie wiązania i zepsuć się, gdy się nie powiedzie i ocenić wyrażenie else.

Ten sam zarzut można znaleźć w komentarzach pod numerem clojuredocs. Znalazłem odpowiedź: here, która tak naprawdę nie zadowoliła, ponieważ plakat wydaje się mieć na myśli ekwiwalent zagnieżdżonej konstrukcji typu "if-let".

Jakie są powody ograniczania powiązań makr * -let?

UPDATE: Jak wydaje się być jasne, jakie są moje oczekiwania IF-let są:

  • Należy ocenić wszystkie powiązania sekwencyjnie.
  • Gdy wszystko się powiedzie, powinno to ocenić "przypadek".
  • Jeśli jedno połączenie nie powiedzie się, powinno natychmiast przerwać i ocenić przypadek "else".
  • W przypadku braku powiązania, a nawet udało ci, nie powinny być dostępne w „else' ekspresji
+2

Nie jest od razu oczywiste, co należy zrobić. Czy powinien się nie powieść przy pierwszym fałszywym wpisie w klauzuli let? A może po prostu przetestować pierwszy wpis, a jeśli to prawda, oceń pozostałe klauzule let do wykorzystania w poniższym kodzie? tj. traktując go jak funkcję zagnieżdżoną: (if-let [a 2] (let [bnil] (+ ab))) - Z pewnością chciałem ten konstrukt również w przeszłości ... – Korny

+0

@Korny: o mnie oczekiwania, zobacz moje zaktualizowane pytanie. – nansen

Odpowiedz

3

if-let i let służyć różnym celom, a jeśli-let nie tylko bardziej ograniczona wersja let . na przykład if-let różni się od let tym, że jest to powiązane tylko dla klauzuli wtedy, a nie inne.

user> (if-let [ans (+ 1 2 3)] ans :foo) 
6 
user> (if-let [ans (+ 1 2 3)] ans ans) 
CompilerException java.lang.RuntimeException: Unable to resolve symbol: ans in this context, compiling:(NO_SOURCE_PATH:1) 
user> (let [ans (+ 1 2 3)] ans ans) 
6 

If-let ma na celu ułatwienie życia w przypadku, gdy wiążą Państwo wartość tylko po to, aby ją przetestować i wykorzystać.

+0

Nie zgadzam się: nie powinieneś używać 'if-let', jeśli wszystko, co będziesz robił w klauzuli' then', zwróci wartość związaną z testem. ja.e, zamiast '(if-let [ans (+ 1 2 3)] ans: foo)' zdecydowanie lepiej jest napisać '(lub (+ 1 2 3): foo)'. 'if-let' jest użyteczne do testowania czegoś, a następnie dalszej manipulacji. – amalloy

+0

@amalloy Być może mogę to wyjaśnić, kod, o którym wspomniałeś, opisuje jeden ze sposobów, w jaki-niech i niech się różnią. Ostatnia linia opisuje jedno z potencjalnych zastosowań if-let. Zmienię słowo powrót do użycia, być może to wyjaśni. –

+0

@ArthurUlfeldt: Nie chcę wiązania w innej klauzuli. Zaktualizowano moje pytanie, aby to wyjaśnić. – nansen

3

Spróbuj tego:

(defmacro if-let-multi 
    ([bindings then-exp] 
    (let [values (take-nth 2 (rest bindings))] 
     `(if (and [email protected]) (let ~bindings ~then-exp) false))) 
    ([bindings then-exp else-exp] 
    (let [values (take-nth 2 (rest bindings))] 
     `(if (and [email protected]) (let ~bindings ~then-exp) ~else-exp)))) 

to jest tutaj w akcji:

user> (if-let-multi [a 2 b nil] (+ a b)) 
false 
user> (if-let-multi [a 2 b 3] (+ a b)) 
5 
user> (if-let-multi [a 2 b nil] (+ a b) "NO WAY") 
"NO WAY" 
+0

Jest to zdecydowanie pomocne. Dzięki! Ale dlaczego, jeśli nie, nie zachowywałeś się w ten sposób? – nansen

1

Spróbuj tego.

 
(defmacro if-lets 
    ([bindings true-expr] `(if-lets ~bindings ~true-expr nil)) 
    ([bindings true-expr false-expr] 
    (cond 
     (or (not (seq bindings)) (not (zero? (rem (count bindings) 2)))) 
     `(throw (IllegalArgumentException. "if-lets requires 2 or multiple of 2 forms in binding vector in user:1")) 
     (seq (drop 2 bindings)) 
     `(if-let ~(vec (take 2 bindings)) 
       (if-lets ~(vec (drop 2 bindings)) 
          ~true-expr 
          ~false-expr) 
       ~false-expr) 
     :else 
     `(if-let ~(vec bindings) 
       ~true-expr 
       ~false-expr)))) 

To makro przeszło poniższe testy poniżej.

 
(deftest ut-if-lets 
    (testing "if-lets macro (normal cases)" 
    (is (= 0 (if-lets [x 0] x))) 
    (is (= 0 (if-lets [x 0] x 1))) 
    (is (= 1 (if-lets [x nil] x 1))) 
    (is (= 0 (if-lets [x 0 y x] y))) 
    (is (= 0 (if-lets [x 0 y x] y 1))) 
    (is (= 1 (if-lets [x nil y x] y 1))) 
    (is (= 0 (if-lets [x 0 y x z y] z))) 
    (is (= 0 (if-lets [x 0 y x z y] z 1))) 
    (is (= 1 (if-lets [x nil y x z y] y 1))) 
    (is (= true (if-lets [x true] true false))) 
    (is (= false (if-lets [x false] true false))) 
    (is (= true (if-lets [x true y true] true false))) 
    (is (= false (if-lets [x false y true] true false))) 
    (is (= false (if-lets [x true y false] true false))) 
    (is (= true (if-lets [x true y true z true] true false))) 
    (is (= false (if-lets [x false y true z true] true false))) 
    (is (= false (if-lets [x true y false z true] true false))) 
    (is (= false (if-lets [x true y true z false] true false))) 
) 
) 

(deftest ut-if-lets-ab 
    (testing "if-lets macro (abnormal cases)" 
    (is (= (try (if-lets [] true false) (catch Exception e (.getMessage e))) 
     "if-lets requires 2 or multiple of 2 forms in binding vector in user:1")) 
    (is (= (try (if-lets [x] true false) (catch Exception e (.getMessage e))) 
     "if-lets requires 2 or multiple of 2 forms in binding vector in user:1")) 
    (is (= (try (if-lets [x true y] true false) (catch Exception e (.getMessage e))) 
     "if-lets requires 2 or multiple of 2 forms in binding vector in user:1")) 
) 
) 
Powiązane problemy