Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create placeholder variables in a try catch block in Clojure

Tags:

clojure

I have some Java code that I would write like this:

String var1;
String var2;
int var3;

try {
    String var1 = func1_that_might_throw();
    String var2 = func2_that_might_throw();
    int var3 = func3_that_might_throw();

    do_something_that_might_throw(var1, var2, var3);
} catch (Exception e) {
    cleanup_resources(var1, var2, var3);
}

Turning this into Clojure is a nightmare.

(try+
  (let [var1 (func1_that_might_throw)
        var2 (func2_that_might_throw)
        var3 (func3_that_might_throw)]
    (do_something_that_might_throw var1 var2 var3))
  (catch Exception e
    (cleanup_resources var1 var2 var3)))

The problem is var1, var2, var3 don't exist in the catch block. Moving the let to outside of try is problematic because functions 1 through 3 might throw and needs to be caught and resources cleaned up.

What I think I might need is just three placeholder variables outside of try, but a) I don't even know if clojure allows this, and b) makes everything more complicated. I don't think this is how a veteran would do this in clojure. What's the solution here?

like image 974
stewart99 Avatar asked Feb 04 '16 22:02

stewart99


4 Answers

Ref/Atom

Without using dynamic variables, you can also put your data into boxes:

(let [v1 (ref nil)
      v2 (ref nil)
      v3 (ref nil)]
    (try
      (dosync
       (ref-set v1 (f1))
       (ref-set v2 (f2))
       (ref-set v3 (f3))
       (do-something @v1 @v2 @v3))
      (catch Exception e
        (cleanup-resources @v1 @v2 @v3))))

Finally

If however you are committed to go the purely immutable way, here is what you should write:

(let [v1 (func1)]
  (try
    (let [v2 (func2)]
      (try
        (let [v3 (func3)]
          (try
            (do
              (try
                (do-something v1 v2 v3)
                (catch Exception e <error-handling>)))
            (finally (cleanup-3 v3))))
        (finally (cleanup-2 v2))))
    (finally (cleanup-1 v1))))

You can see that cleanup-resources is split into three functions, which in my opinion would make sense also in your Java code in the first place. The above was generated using a macro:

(defmacro with-cleanups [bindings & body]
  (reduce
   (fn [form [v value cleanup]]
     `(let [~v ~value]
        (try ~form
             (finally ~cleanup))))
   `(do ~@body)
   (reverse (partition 3 bindings))))

Here is the actual code that you need to write:

(with-cleanups [v1 (func1) (cleanup-1 v1)
                v2 (func2) (cleanup-2 v2)
                v3 (func3) (cleanup-3 v3)]
  (try
    (do-something v1 v2 v3)
    (catch Exception e <error-handling>)))

If you prefer, you could let the cleanup part of the bindings be a function instead of an expression:

(with-cleanups [v1 (func1) cleanup-1
                v2 (func2) cleanup-2
                v3 (func3) cleanup-3]
  (try
    (do-something v1 v2 v3)
    (catch Exception e <error-handling>)))
like image 137
coredump Avatar answered Oct 04 '22 04:10

coredump


This is very much a question of program design and structure. It sounds like a chicken-and-egg problem regarding resource state management. Rather then solve this problem it's likely to be much less painful if you can break that problem down into :

  • a function that creates your state and handles errors in creating the state
  • another function that does the work and handles the errors caused while doing the work.

These are separate problems being solved by one function. Things tend to get easier if you can pry these things apart.

Now, with the preaching aside, clojure has many ways of handling mutable state, and does not pretend that the whole world can be convinced to do things "the right way", and Clojure does allow you to simply create and set variables just line in java. The reason you never see these in practice is because they are always a sign that the problem is being solved in a backwards way or is being mixed with another different problem.

TRY EVERYTHING YOU CAN TO AVOID WRITING CODE LIKE THIS:

user> (def ^:dynamic var1)
#'user/var1
user> (def ^:dynamic var2)
#'user/var2
user> (def ^:dynamic var3)
#'user/var3

user> (defn do-stuff []
        (binding [var1 nil
                  var2 nil
                  var3 nil]
          (try
            (set! var1 42)
            (set! var2 43)
            (set! var3 44)
            (+ var1 var2 var3)
            (catch Exception e
              e))))
#'user/do-stuff
user> (do-stuff)
129

I have never seen code like this used in any practical problem (I've been writing Clojure as my day job about as long as anyone) and these things basically never come up. I'm wirting it here because I don't want people comming away with the impression that there are things you "can't write" in Clojure.

It's important to emphasize that this code has all the concurrency safeties that the rest of clojure has. Because binding creates thread local values that follow this code while isolating other users of var1, var2, etc. unaffected.

One more resonable way of doing this is to use futures (delay creates these) to define the computation to be run outside the try-catch, without actually running it

Then the code enters a try catch block and derefrences the delayed code which causes it to actually run.

user> (defn do-stuff []
        (let [var1 (delay (inc 41))
              var2 (delay (dec 43))
              var3 (delay (/ 42 0))]
          (try
            (+ @var1 @var2 @var3)
            (catch Exception e
              (println "cleaning up: " var1 var2 var3)))))
#'user/do-stuff
user> (do-stuff)
cleaning up:  #object[clojure.lang.Delay 0x3c55d284 {:status :ready, :val 42}]
              #object[clojure.lang.Delay 0x7bfa6dd1 {:status :ready, :val 42}]
              #object[clojure.lang.Delay     0x5e9e285b {:status :failed, :val #error {
              :cause Divide by zero

This assumes you can change your cleanup code to take a reference to something that may contain either a value to cleanup, or the exception that caused the error. delays have the useful property that the exception can be thrown in the main function, and then be thrown again in the error handler. Ponder this example:

user> (def x (delay (/ 2 0)))
#'user/x
user> @x
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)
user> @x
ArithmeticException Divide by zero  clojure.lang.Numbers.divide (Numbers.java:158)

You should certainly solve this problem by breaking your function into two, and not by using set! on dyanamic vars. It's just possible.

like image 44
Arthur Ulfeldt Avatar answered Oct 04 '22 03:10

Arthur Ulfeldt


You can write this kind of code simply in Java and Clojure in the same way. For every function that might cause an exception have that exception as part of the return value. In Java you would return an instance of a class that would have for example a getErrors() method. In Clojure you could return a hash-map or possibly a tuple-2 vector. The hash-map could for example contain the exception: {:exception nil} You would need to do this with all of 4 your functions: do_something_that_might_throw and the other three.

In Clojure you can then map/filter over the results. Exceptions can be handled when they need to be (locally, or as in your case collectively (but still quite locally)) and not proliferate throughout the codebase.

like image 25
Chris Murphy Avatar answered Oct 04 '22 03:10

Chris Murphy


update: refactored macro to take code blocks instead of functions

like @coredump, i would also propose a macro for that, but it's closer to the op's variant (in which he cleans up all the resources in one block):

(defmacro with-resources [[var expr & other :as resources]
                          body cleanup-block
                          [error-name error-block :as error-handler]]
  (if (empty? resources)
    `(try ~body
          (catch Throwable e#
            (let [~error-name e#] ~error-block))
          (finally ~cleanup-block))
    `(try
       (let ~[var expr]
         (with-resources ~other ~body ~cleanup-block ~error-handler))
       (catch Throwable e#
         (let ~(vec (interleave (take-nth 2 resources)
                                (repeat nil)))
           ~cleanup-block
           (let [~error-name e#] ~error-block))))))

this is how it works: it initializes the resources, and if something is thrown from initializers or body if executes cleanup-block with all resources (or nils for uninitialized ones), and error-block with an exception bind to the error-name, and returns its value. It nothing throws, just calls the cleanup-block and returns the value of body.

examples:

user> (with-resources [a 100 b 200 c 300]
        ;;body
        (+ a b c)
        ;;cleanup
        (println "cleanup:" :a a :b b :c c)
        ;;error handler
        (error (do (println "Error caught:" (.getMessage error))
                   :error)))

cleanup: :a 100 :b 200 :c 300
600

user> (with-resources [a (throw (Exception. "A THROWS")) b 200 c 300]
        ;;body
        (+ a b c)
        ;;cleanup
        (println "cleanup:" :a a :b b :c c)
        ;;error handler
        (error (do (println "Error caught:" (.getMessage error))
               :error)))

cleanup: :a nil :b nil :c nil
Error caught: A THROWS
:error

user> (with-resources [a 100 b (throw (Exception. "B THROWS")) c 300]
        ;;body
        (+ a b c)
        ;;cleanup
        (println "cleanup:" :a a :b b :c c)
        ;;error handler
        (error (do (println "Error caught:" (.getMessage error))
                   :error)))

cleanup: :a 100 :b nil :c nil
Error caught: B THROWS
:error

user> (with-resources [a 100 b 200 c (throw (Exception. "C THROWS"))]
        ;;body
        (+ a b c)
        ;;cleanup
        (println "cleanup:" :a a :b b :c c)
        ;;error handler
        (error (do (println "Error caught:" (.getMessage error))
                   :error)))

cleanup: :a 100 :b 200 :c nil
Error caught: C THROWS
:error

user> (with-resources [a 100 b 200 c 300]
        ;;body
        (throw (Exception. "BODY THROWS"))
        ;;cleanup
        (println "cleanup:" :a a :b b :c c)
        ;;error handler
        (error (do (println "Error caught:" (.getMessage error))
                   :error)))

Error caught: BODY THROWS
cleanup: :a 100 :b 200 :c 300
:error
like image 31
leetwinski Avatar answered Oct 04 '22 03:10

leetwinski