Skip to content

(Yet another) simple condition system for Clojure.

License

Notifications You must be signed in to change notification settings

hanjos/conditio-clj

Repository files navigation

A simple condition system for Clojure, without too much machinery.

CI Docs Git package Maven package

Latest

Git

org.sbrubbles/conditio-clj {:git/url "https://github.com/hanjos/conditio-clj" 
                            :git/tag "0.2.0"  :git/sha "d99c9c3"}

Maven

Configure your settings.xml:

<servers>
    <server>
        <id>github</id>
        <username>YOUR_GITHUB_LOGIN</username>
        <password>YOUR_AUTH_TOKEN</password>
    </server>
</servers>

Add this repo to your deps.edn:

:mvn/repos {"github" {:url "https://maven.pkg.github.com/hanjos/conditio-clj"}}

And then:

org.sbrubbles/conditio-clj {:mvn/version "0.2.0"}

What

Exception systems divide responsibilities in two parts: signalling the exception (like throw), and handling it (like try/catch), unwinding the call stack until a handler is found. The problem is, by the time the error reaches the right handler, the context that signalled the exception is mostly gone. This limits the recovery options available.

A condition system, like the one in Common Lisp, provides a more general solution by splitting responsibilities in three parts: signalling the condition, handling it, and restarting execution. The call stack is unwound only if that was the handling strategy chosen; it doesn't have to be. This enables novel recovery strategies and protocols, and can be used for things other than error handling.

Beyond Exception Handling: Conditions and Restarts, chapter 19 of Peter Seibel's Practical Common Lisp, informs much of the descriptions (as one can plainly see; I hope he doesn't mind 😁), terminology and tests.

Why?

I haven't used Clojure in years, so this seemed as good an excuse as any 😄

It's an opportunity to remove some cobwebs and check out some "new" stuff, such as deps.edn, transducers, maybe spec (I said it has been some time...). Let's see how far I go...

How?

Well, with binding and dynamic variables, most of the machinery is already there, so life is a lot easier 😄

The end result should look something like this:

(ns user
  (:require [org.sbrubbles.conditio :as c]))

; This example draws from Practical Common Lisp, but with some shortcuts 
; to simplify matters 

(defn parse-log-entry [line]
  (if (not= line :fail) ; :fail represents a malformed log entry
    line
    ; adds :user/retry-with as an available restart
    (c/with [::retry-with parse-log-entry]
      ; signals :user/malformed-log-entry 
      (c/signal ::malformed-log-entry :line line))))

(defn parse-log-file []
  ; creates a function which calls parse-log-entry with :user/skip-entry 
  ; as an available restart. Any entries which return 'skip-entry will
  ; be skipped
  (comp (map (c/with-fn {::skip-entry (fn [] 'skip-entry)}
                        parse-log-entry))
        (filter #(not= % 'skip-entry))))

(defn analyze-logs [& args]
  ; handles :user/malformed-log-entry conditions, opting to restart 
  ; with :user/skip-entry
  (c/handle [::malformed-log-entry (fn [_] (c/restart ::skip-entry))]
    (into []
          (comp cat
                (parse-log-file))
          args)))

; every vector is a 'file'
(analyze-logs ["a" "b"]
              ["c" :fail :fail]
              [:fail "d" :fail "e"])
;; => ["a" "b" "c" "d" "e"]

Using

deps.edn, tools.build and codox for docs.

Caveats and stuff to mull over

  • Despite what the name might suggest, I didn't try to maintain parity with conditio-java, although almost everything is there.
  • I could've used vars instead of keywords. For now, I'm going with keywords. Vars seem to require too much magic...

About

(Yet another) simple condition system for Clojure.

Resources

License

Stars

Watchers

Forks

Packages