2020-12-23 15:51:22 +00:00
|
|
|
#+TITLE: Effects system in Clojure
|
|
|
|
#+Author: Yann Esposito
|
|
|
|
#+Date: [2020-12-23]
|
|
|
|
|
|
|
|
tags :: [[file:2020-05-26--06-16-14Z--clojure.org][clojure]]
|
|
|
|
source ::
|
2020-12-23 15:55:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
Warning foreword. The Effect system (see type à la carte) from the Freer
|
|
|
|
Monad article takes in root in a precise definition.
|
|
|
|
Here the notion of Effect system will probably feel totally skewed from the
|
|
|
|
perspective of this article.
|
|
|
|
But I will talk about how to use the same ideas in a dynamicly typed
|
|
|
|
programming language. And in particular Clojure.
|
|
|
|
|
|
|
|
On a high level perspective the end goal of an Effect system is to be able
|
|
|
|
to write program such that the semantic of the program will depend on the
|
2020-12-23 15:56:47 +00:00
|
|
|
context the program is executed in.
|
|
|
|
|
|
|
|
So for example, let's say you write a program like this:
|
|
|
|
|
|
|
|
#+begin_src clojure
|
2020-12-23 15:58:23 +00:00
|
|
|
(defn my-action []
|
|
|
|
(let [x (first-action)
|
|
|
|
y (second-action)
|
|
|
|
z (third-action x y)]
|
|
|
|
(if z
|
2020-12-23 16:21:35 +00:00
|
|
|
(do
|
|
|
|
(log "x = " x)
|
|
|
|
(fourth-action x))
|
2020-12-23 15:58:23 +00:00
|
|
|
(fifth-action y))))
|
|
|
|
#+end_src
|
|
|
|
|
|
|
|
Generally you expect every =*-action= to be a well defined function.
|
|
|
|
You can take a look at that function and see what code will be execute.
|
|
|
|
|
2020-12-23 15:59:59 +00:00
|
|
|
Within an effect system, you would be able to write something like this:
|
2020-12-23 15:58:23 +00:00
|
|
|
|
|
|
|
#+begin_src clojure
|
2020-12-23 15:59:59 +00:00
|
|
|
(with-effects
|
|
|
|
[logs-eff
|
|
|
|
db-eff]
|
|
|
|
(my-action))
|
|
|
|
|
|
|
|
(with-effects
|
|
|
|
[test-logs-eff
|
|
|
|
test-db-eff]
|
|
|
|
(my-action))
|
2020-12-23 15:56:47 +00:00
|
|
|
#+end_src
|
2020-12-23 16:01:47 +00:00
|
|
|
|
|
|
|
You can imagine that =logs-eff= and =db-eff= to be /Effects/ the first
|
|
|
|
could be used to write logs and the second one will be able to access a DB.
|
|
|
|
And in the second bloc =test-logs-eff= would be a completely pure
|
|
|
|
implementation of the =logs= and a fake =db= implementation.
|
2020-12-23 16:07:12 +00:00
|
|
|
|
|
|
|
So that way of writting your code is extremely appealing because it will
|
|
|
|
help a lot to test it.
|
|
|
|
Mainly you only need to change the list of effects used in your main
|
|
|
|
function and you will have the same code you will use in production to only
|
|
|
|
use pure effect. And thus will be completely reproductible and easy to test.
|
|
|
|
|
2020-12-23 16:09:42 +00:00
|
|
|
Effects systems while used in some languages (like Purescript, and some
|
2020-12-23 16:16:05 +00:00
|
|
|
libs exists in Haskell) does not really appear to be a very hot subject in
|
|
|
|
the Clojure community.
|
|
|
|
One of the reason might be because in Clojure the concepts and design space
|
|
|
|
is occupied with different existing solutions to a similar solution.
|
|
|
|
Mainly, compononent and trapperkeeper libraries for example.
|
|
|
|
Unlike effects, component and trapperkeeper have a few advantages.
|
|
|
|
I will not talk too much about component as I never used it.
|
|
|
|
So I will use trapperkeeper to explain myself.
|
|
|
|
One major advantage is the ability to describe dependecies between
|
|
|
|
different Effects in trapperkeeper.
|
|
|
|
Typically, one can simply imagine you would like to create an Effect to
|
|
|
|
handle the persistence of your Users.
|
2020-12-23 16:17:47 +00:00
|
|
|
It would seems natural that to provide an API to manipulate Users it will
|
|
|
|
need a DB related effect.
|
|
|
|
And thus you will also probably need to describe the fact that you need the
|
2020-12-23 16:18:58 +00:00
|
|
|
DB effect to be "ready" before being able to provide the User Effect.
|
2020-12-23 16:16:05 +00:00
|
|
|
|
|
|
|
So instead of talking about "Effects" we are talking about "Services".
|