deft/journal/2020-12-23--16-50-17Z--effects_system_in_clojure.org
2020-12-23 17:36:57 +01:00

101 lines
3.6 KiB
Org Mode

#+TITLE: Effects system in Clojure
#+Author: Yann Esposito
#+Date: [2020-12-23]
tags :: [[file:2020-05-26--06-16-14Z--clojure.org][clojure]]
source ::
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
context the program is executed in.
So for example, let's say you write a program like this:
#+begin_src clojure
(defn user-logged-in [user-id]
(if-let [user (get-user user-id)]
(do
(log "user found!")
(update-last-time-logged user))
(throw {:error :user-not-found
:user-id user-id})))
#+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.
Within an effect system, you would be able to write something like this:
#+begin_src clojure
(with-effects
[logs-eff
db-eff]
(my-action))
(with-effects
[test-logs-eff
test-db-eff]
(my-action))
#+end_src
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.
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.
Effects systems while used in some languages (like Purescript, and some
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.
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
DB effect to be "ready" before being able to provide the User Effect.
So instead of talking about "Effects" we are talking about "Services".
They start, they stop and you can use functions they provide and the
services will take care about their internal state.
This has similarities with object oriented programming, but it sounds a lot
better to use this system in a functional programming context.
The generic pattern will be something like:
#+begin_src clojure
(defprotocol Protocol
(fn-of-protocol [this params]))
(defservice ServiceName
Protocol
[OtherService1
OtherService2]
(init [this context])
(start [this context])
(stop [this context])
(fn-of-protocol [this params]))
#+end_src
So when you need to test your service you can replace a service with a
stubbed one.