Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error handling in cljc macro

I found it surprisingly tricky to define a macro to do error handling in both clj and cljs. I assumed it was a simple matter of swapping Exception with js/Error, but it turned out to be more complicated than that.

At first, I tried this:

(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch #?(:clj Exception :cljs js/Error) e#
      ,,,)))

But this produced Exception every time. I soon realized that the problem was that the macro was being called during the compilation of my cljs files, which happens in a clj environment. Therefore, I would have to have the macro return a form that would resolve to the correct exception class at runtime. I tried this:

(def exception-class
  #?(:clj Exception :cljs js/Error))

(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch exception-class e#
      ,,,)))

Now it worked in cljs, but not in clj!!! After some experimentation, I discovered that JVM Clojure (apparently) does not allow you to refer to the exception class indirectly. You have to refer to Exception directly by name.

So finally, I settled on this:

(def fake-java
  #?(:cljs (clj->js {:lang {:Exception js/Error}})))

(defmacro my-macro
  [& forms]
 `(let [~'java fake-java]
    (try
      ~@forms
      (catch Exception e#
        ,,,))))

Exception expands to java.lang.Exception, which now resolves to the correct exception class at runtime in both clj and cljs.

My question is, is there a better way to this? And why does JVM Clojure not allow referring to the exception class indirectly, but ClojureScript does?

Update

With ClojureMostly's help, I have refactored the macro like this, and it works:

(defmacro my-macro
  [& forms]
 `(try
    ~@forms
    (catch ~(if (:ns &env) 'js/Error 'Exception) e#
      ,,,)))
like image 737
grandinero Avatar asked Jan 07 '17 00:01

grandinero


1 Answers

You could refactor your macro to be expressed in terms of a function call. This function would accept a "thunk" of the forms and wrap it in the try (rather than doing this directly in the macro).

For example:

(defmacro my-macro [& forms]
  `(my-macro* (fn []
                ~@forms)))

Then you can define my-macro* as:

(defn my-macro* [f]
  (try
    (f)
    (catch #?(:clj Exception :cljs js/Error) e
      ...)))
like image 181
Nathan Davis Avatar answered Nov 09 '22 16:11

Nathan Davis