deft/notes/either_in_clojure.org

118 lines
3.5 KiB
Org Mode
Raw Permalink Normal View History

2022-11-01 19:52:02 +00:00
:PROPERTIES:
:ID: b413e4db-1367-4936-8a46-cd5b86178e29
:END:
#+title: Either in Clojure
#+author: Yann Esposito
#+date: [2022-08-15]
2024-08-14 09:35:42 +00:00
- tags :: [[id:a5be1daf-1010-428f-a30f-8faf95c1a42f][blog]]
2022-11-01 19:52:02 +00:00
- 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:
#+begin_src clojure
;; 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)))
#+end_src
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:
#+begin_src clojure
{:type (enum :left :right)
:content value-or-error}
;; example for (Right 42)
{:type :right
:content 42}
;; exaple for (Left "error")
{:type :left
:content "error"}
#+end_src
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).
#+begin_src clojure
{: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"}
#+end_src
And the last one not as generic, but good enough for our use case, both terser,
easier to read and manipulate:
#+begin_src clojure
{(optional-key val): Value
(optional-key err): Error}
;; example for (Right 42)
{:val 42}
;; example for (Left "error")
{:err "error"}
#+end_src
So we'll choose this one.