deft/notes/either_in_clojure.org
Yann Esposito (Yogsototh) c1d2459d0c
save
2024-08-14 11:35:42 +02:00

3.5 KiB

Either in Clojure

tags
blog
source

Either Either or Exceptions?

When you start working with Clojure (or many other programming language) a classic notion of error is, for your function, to return nothing is an error occurred. But quite often you want to convey more detail about the error so you could act differently depending on it. Or provide a more detailed error message to your end user, etc…

The recommended method to handle errors in Clojure (and a lot of other programming languages) is to throw exceptions. While this is perfectly pragmatic and reasonable, exceptions have a few undesirable properties.

First, they are impure, which somehow break the principles behind functional programming. So if you are using Clojure chance are you prefer to keep it as pure as possible.

Second, exceptions are generally expensive, and should probably be avoided if possible. (TODO: write a short benchmark).

Third, Exceptions lack of composability properties and are often hidden. It is generally impossible to know if a function could or could not throw an exception. And also if you should or should not do something about it. Also, while possible it is not very natural to accumulate errors generated by a sequence of actions. The same could be said if you want to transform the error depending on the code context. Here are two example of code:

;; Example of accumulating values and errors
(for [x things]
  (try (do-some-action %)
       (catch Exception e
         (do-something-with-exception e))))

;; Example of tranforming value
(try (do-some-action x)
     (catch Exception e
       (transform e)))

We see this is easy, but, I don't know, it looks a bit cumbersome to me.

So what is the other possibility?

In the Statically Typed Pure Function Programming world people often use Either. What is this? Mainly this is a type that represent the notion of: "We have either something or an error."

Either contains two component, by convention a left and a right. And also by convention the right contain the value while the left contain the error. Why? Because right also means correct in English.

So Either is a sum type. Sum types is probably THE feature I miss the most in many programming languages. Anyway, it is not difficult to simulate a sum type. Here are a few possible representations:

Using a tag field that indicate if the content is a right or a left:

{:type (enum :left :right)
 :content value-or-error}

;; example for (Right 42)
{:type :right
 :content 42}
;; exaple for (Left "error")
{:type :left
 :content "error"}

Another way to prevent some potential issue with conflicting different schemas (imagine if you had to save the previous representation within Elasticsearch for example, the mapping would probably be difficult to write).

{:type (enum :left :right)
 (optional-key :left) Error
 (optional-key :right) Value}
;; example for (Right 42)
{:type :right
 :right 42}
;; exaple for (Left "error")
{:type :left
 :left "error"}

And the last one not as generic, but good enough for our use case, both terser, easier to read and manipulate:

{(optional-key val): Value
 (optional-key err): Error}
;; example for (Right 42)
{:val 42}
;; example for (Left "error")
{:err "error"}

So we'll choose this one.