tags:

views:

258

answers:

3

Still trying to wrap my head around Clojure. I can see how to implement the following in Haskell, Python, etc. but do not yet understand how to write this in Clojure. Appreciate if someone can show me the basic structure. Pseudo-code below.

a = get_a
if (a == bad_value) then throw exception_a
b = get_b
if (b == bad_value) then throw exception_b
c = get_c
if (c == bad_value) then throw exception_c
...
do_action_with a b c

Would this be a bunch of lets and then a final expression? Thanks.

+13  A: 

There is a number of possibilities -- here's a few for a start:

;;; 1. direct translation
; _ is the idiomatic "I don't care" identifier in Clojure
(let [a (get-a)
      _ (if (= a bad-value) (throw (Exception. "Foo!")))
      b (get-b)
      _ (if (= b bad-value) (throw (Exception. "Foo!")))
      ...]
  (do-action-with a b ...))

;;; 2. abstract the pattern away
(defmacro disallow
  ([expr val] ; binary version with default exception type;
              ; omit if explicit type is to be required
     (disallow (list* &form [Exception]) &env expr val Exception))
  ([expr val e]
     `(let [actual# ~expr]
        (if (= actual# ~val)
          (throw (new ~e (str "Value " ~val " not allowed.")))
          actual#))))

(let [a (disallow (get-a) ExceptionA)
      b (disallow (get-b) ExceptionB)
      ...]
  ...)

;;; 3. monadic short-circuiting
(use '[clojure.contrib.monads :only [domonad maybe-m]])
; ...now do it more or less as in Haskell;
; you can use :when in domonad for monads with m-zero
; -- the syntax is that of for / doseq:
(doseq [sym '[a b c]] (intern *ns* sym (atom 0)))
(domonad maybe-m
  [a @a
   :when (pos? a)
   b @b
   :when (neg? b)
   c @c
   :when (not (zero? c))]
  (* a b c))
; => 0
(dorun (map reset! [a b c] [3 -2 1]))
(domonad maybe-m
  ; same as above
  )
; => -6
Michał Marczyk
Excellent answer, again. +1. My answer is removed.
Isaac Hodes
@Isaac Hodes: Thanks! By the way, I wonder if writing a *function* instead of a macro is an option if we're not willing to hardcode a single exception type for all invocations... Using `proxy` for exception classes feels silly, but maybe c.c.condition / c.c.error-kit could help? I really need to investigate those properly sometime soon.
Michał Marczyk
I haven't looked at either of those–they look interesting, and pretty capable…might need to explore these. Thanks for the pointers!
Isaac Hodes
Shouldn't "~" go before "e" and "val" not "(new"?
ubolonton
@ubulonton: Good catch, thanks! Fixed now. Also new in this update -- extra comments, a variadic version of `disallow` and examples of monadic short-circuiting.
Michał Marczyk
... *also* new in last update: some bugs. Here's hoping all are fixed now.
Michał Marczyk
+3  A: 

A version without macros, untested, but I think this would work:

(defn check [bad xs]
  (for [[f e] (partition 2 xs) :let [x (f)]]
    (if (= x bad)
      (throw (e)) 
      x)))

(let [[a b c] (check bad [get-a #(ExceptionA.)
                          get-b #(ExceptionB.)
                          get-c #(ExceptionC.)])]
  (do-action-with a b c))

I wrapped the exceptions in functions so that they aren't generated unless you need to throw them. Michał's version with macros is cleaner.

Brian Carper
A: 

you can use cond :

(cond
  (= a bad _value) exception_a
  (= b bad _value) exception_b
  (= c bad _value) exception_c
  :else default)

the else is optional ;)

PS: I dont know how to throw an exception in Clojure so don't tell me that the code doesn't work

Aymen