MichaelDrogalis / dire

Erlang-style supervisor error handling for Clojure
483 stars 19 forks source link

Use function metadata to specify any Dire functionality you want to use #30

Open MatthewDarling opened 9 years ago

MatthewDarling commented 9 years ago

(this is currently a WIP, and only supports the features of Dire that I use personally, but I think it will be trivial to extend)

This allows the user to forget about all the with-X, remove-X functions and use a single function to either add or remove anything they want attached to the function.

Example usage

(defn ^{::preconditions '[precondition]
        ::handlers {::pre-handlers '[pre-handler]}
        ::eager-pre-hooks '[eager-pre-hook]
        ::wrap-hooks '[wrap-hook]}
  test-fn
  "Docstring"
  [a b]
  (/ a b))

(apply-dire-meta! test-fn)
(remove-dire! test-fn)

Rationale

I really like the separation of concerns that Dire enables, but as I was converting the project I work on to use Dire, I realized that I couldn't just add a precondition and forget about it entirely. In order to understand the possible return values of the function, I needed to look at what the handler would do with failure cases, and that made the code more difficult to reason about.

So I wanted a way to add metadata to functions in order to tell readers of the code that there was more going on besides what was visible in the current namespace.

The other issue I had was that, because I was using the mutating functions, loading the namespaces that used Dire would cause side effects, and that's not great. When I saw the example of using Dire with Component, I knew that was a better way of doing things, but I really didn't want to add/remove dozens of handlers and hooks manually.

Remaining issues

MatthewDarling commented 9 years ago

Example of the fact that it doesn't work:

dire.metadata> (apply-dire-meta! #'test-fn)
({:dire.core/supervisor-hook-key #<core$partial$fn__4190 clojure.core$partial$fn__4190@44708681>})
dire.metadata> (clojure.pprint/pprint (meta (var test-fn)))
{:arglists ([a b]),
 :dire/eager-pre-hooks #{eager-pre-hook},
 :dire/preconditions {:pre precondition},
 :ns #<Namespace dire.metadata>,
 :name test-fn,
 :dire.metadata/handlers {:dire.metadata/pre-handlers [pre-handler]},
 :dire.metadata/wrap-hooks [wrap-hook],
 :column 1,
 :dire/error-handlers {{:precondition :pre} pre-handler},
 :dire.metadata/eager-pre-hooks [eager-pre-hook],
 :doc "Docstring",
 :dire.metadata/preconditions [precondition],
 :dire/wrap-hooks #{wrap-hook},
 :line 75,
 :file "/Users/matthewdarling/src/dire/src/dire/metadata.clj"}
nil
dire.metadata> (test-fn 10 2)
5

(also, apologies for the probable e-mail spam, I keep seeing issues that need to be fixed before anyone else can actually try this out)

MichaelDrogalis commented 9 years ago

This is a terrific idea! Thanks for contributing! I, too, wanted something along these lines. I never had time to chase it down though.

I don't have time to look at this today, but I'll try to diagnose what's happening in the next few days. I think this would be a massive step forward for Dire if we can pull it off, though. Maybe try Alex Miller in #clojure if you're totally stumped. He's a pro with the innards of Clojure. :)

MichaelDrogalis commented 9 years ago

@MichaelDrogalis (Tagging myself so I get email notifications, GitHub is pretty terrible about this)

ghost commented 8 years ago

I wanted to do this as well and was disappointed when I found this on google and it wasn't working. I dived a bit deeper to see if I could figure it out myself, and it seems dire already does this? Or at least, does what I needed (and thought this was):

(require '[dire.core :as dire])
(defn ^{:dire/preconditions {:not-three (fn [x] (= x 3))}} increase [x] (inc x))
(increase 3)                            ;=> 4
(increase 2)                            ;=> 3
(dire/supervise #'increase 3)           ;=> 4
(dire/supervise #'increase 2)           ;=> ExceptionInfo throw+: {:type :dire.core/precondition, :precondition :not-three}
(dire/hook-supervisor-to-fn #'increase) ;=> {:dire.core/supervisor-hook-key #<core$partial$fn__4228 clojure.core$partial$fn__4228@72ad15e6>}
(increase 3)                            ;=> 4
(increase 2)                            ;=> ExceptionInfo throw+: {:type :dire.core/precondition, :precondition :not-three}  dire.core/eval1887/fn--1888 (core.clj:210)