deft/notes/2021-01-03--12-08-46Z--voeux_2021_alexandre.org
Yann Esposito (Yogsototh) d641cd04d9
moved files
2021-09-15 09:12:29 +02:00

181 lines
7.2 KiB
Org Mode

:PROPERTIES:
:ID: 0a550da5-55e8-4cf4-ae29-1aa0240ed27d
:END:
#+TITLE: voeux 2021 alexandre
#+Author: Yann Esposito
#+Date: [2021-01-03]
- tags ::
- source ::
Bonjour Alexandre, je te souhaite à toi et toute ta famille une très bonne
année 2021 !
Je vous souhaite à tous et à votre famille une très bonne santé surtout
dans le climat actuel.
De trouver un bon équilibre entre la vie familiale, les passions et le
travail.
Et je me permet d'ajouter à ce mail une réflexion technique entre Haskell
et Clojure qui je l'espère t'inspirera.
Bien que je n'écrive plus vraiment de nouvel article sur
Haskell/Purescript, j'ai passé pas mal de temps à réfléchir à comment
architecturer du code dans ces langages et Clojure.
J'ai l'impression qu'une solution qu'on utilise actuellement en Clojure
bien qu'à première vue très inférieure aux solutions proposées par
Haskell/Purescript sont en fait supérieure lorsqu'on les regardes selon le
bon angle.
L'idée de fond c'est que les systèmes d'effets/Free Monad (et les
sous-systèmes, style MTL, handler pattern) sont des cas particuliers
restreints de ce que l'on peut obtenir avec un système de "Services".
Si on regarde l'intérêt de ces systèmes d'organisation de code --
le plus évolué étant les Free Monads et les systèmes d'effets --
leur objectif est toujours plus ou moins le même.
Changer le comportement du même bloc de code en fonction du contexte
d'exécution.
En gros si on prend une function qui ressemble à:
#+begin_src haskell
my_function arg1 ... argn = do
x1 <- action1 arg1 .. argn
x2 <- action2 arg1 .. argn x1
...
z <- actionK arg1 .. argn x1 .. x<K-1>
return z
#+end_src
les =action1= à =actionK= auront des comportement différents en fonction du
contexte dans lequel il va être initialisé.
Dans le cas de Haskell, on fait au mieux pour déterminer le contexte à
compile time en utilisant les types.
Du genre:
#+begin_src haskell
main = do
effect1 <- initEffect1
effect2 <- initEffect2 effect1
...
effectM <- initEffectM effect1 ... effect<M-1>
runWithEffects [effect1,effect2,...,effectM] my_function
#+end_src
Ce système présente ainsi beaucoup d'avantanges:
1. mettre toute la "business logic" dans la fonction =my_function= en se
débarrassant des détails techniques au mieu en les envoyant dans les
"Effets".
2. Pouvoir facilement changer les Effects par des Effects purs et pouvoir
ainsi écrire des tests déterministes et reproductibles aisément.
3. Controller "par le haut" les sous-effets disponibles dans certaines
branches du code. On peut du coups facilement contraindre et voir que
certaines fonction ne pourront jamais accéder à la DB par exemple.
Par contre ce système à un coût sur les performances.
Je pense que le coût est négligeable dans la majorité des cas et qu'il
s'agit d'"Haskell circle jerking" lorsque les gens refusent d'utiliser
des systèmes d'effects pour cette raison.
Si on va au bout de l'utilisation de tel système en réalité on voit qu'il
faut, plutôt qu'utiliser des systèmes tout prêt à l'emploi, se créer
beaucoup d'effects maison pour en tirer le meilleur parti.
Typiquement on va avoir des effects avec leur pendant pur qui devraient
probablement être standards comme un effect de Log, d'accès complet à une
DB peut-être ?
Mais assez vite, on a envie de se fabriquer des Effets spécialisé au
domaine de son application.
Typiquement, si on fait de la gestion d'utilisateur on va créer un effet
pour la gestion d'utilisateurs plutôt que filer un Effet complet d'accès à
la DB.
Ce qui permet de s'assurer que des fonctions n'auront accès qu'aux tables
de la DB utilisées pour les utilisateurs et pas pour les tables destinées à
d'autres buts etc...
Et c'est là qu'apparaît la première limitation des systèmes d'Effets.
Les effets ont des dépendances entre eux.
L'effet =UserEffect= doit dépendre de l'effect =DBEffect=.
Mais probablement aussi de l'effet qui permet d'écrire des logs, et de pas
mal d'autres.
Et c'est à la lumière de voir ces effects comme des outils d'applications
pragmatiques et pas juste des "Effect algébriques" que l'on voit apparaître
un besoin de gestion de ces dépendances.
Un aspect important devient donc la gestion de l'ordre d'init et de stop de
tous ces effets.
En effet, pour faire =initUserEffect= il faudra lui passer en paramètre des
effets dejè instanciés (et donc initialisés) pour les logs et la DB.
Un premier problème est donc que changer/modifier des dépendances entre
effet dans un point du système revient à imposer une modification manuelle
du code dans tous les effets dépendants.
Mais aussi en particulier le code de l'init du système devient lourd à
gerrer alors qu'il existe une solution simple.
Je pense que Tardis pourrait être utile dans ce cas, mais, ça ne me semble
pas encore assez facile.
Même avec tardis, il me semble qu'il faut savoir si un effet doit arriver
avant ou après.
Là on souhaiterai pouvoir utiliser la lazyness pour une gestion
automatique des dépendances.
En réalité l'API la plus souhaitable serait de laisser la gestion de
l'ordre des dépendance et leur appel laissé à un système automatique.
Ainsi au lieu d'avoir
#+begin_src haskell
main = do
confEffect <- initConfEffect "config.dhall"
dbEffect <- initDBEffect confEffect
userEffect <- initUserEffect dbEffect confEffect
runWithEffects [confEffect,dbEffect,userEffect] myFunction
#+end_src
Il semble préférable d'écrire:
#+begin_src haskell
main = do
let effects = [ ConfEff, DBEff, UserEff ]
runWithEffects effects myFunction
#+end_src
Sans avoir à se poser la question de quel effet doit utiliser quel autre.
Ce sont des détails qui doivent être dans les Effets eux-même et pas donné
à une gestion manuelle par l'utilisateur.
Et c'est exactement ce que les systèmes de services font en clojure.
Il en existe plusieurs.
Component [fn:1] et Trapperkeeper [fn:2].
Je travaille avec le second.
Evidement en Clojure tout se gère en runtime.
Mais si j'ai bien lu et compris la présentation sur les différents systèmes
d'Effets il semble difficile de faire beaucoup mieux en Haskell.
Il est en effet très difficile de faire descendre dans le code le détail
qui permet de savoir dans quel contexte il sera exécuté et changer le code
pour utiliser seulement le bon contexte et l'optimiser en conséquence.
Finalement, j'ai vu des essais pour exploiter ces idées en Haskell.
Mais elles sont toutes faiblardes et échouent.
J'ai moi-même essayé.
Le probléme de fond vient du fait qu'il est quasi impossible de trouver un
type qui soit à la fois assez generique pour matcher ce que doit faire un
Service et assez précis pour être utile lors du typage du code.
Voici donc un point qui m'agace au plus haut point.
D'habitude je préfère défendre les approches à la Haskell.
Mais pour le coups, impossible de faire aussi bien que Clojure et de plus
sur un des sujets les plus chauds actuellement dans la communauté.
Peut-être que l'écriture de cet email me donnera l'élan nécessaire pour
communiquer cette constation et le publier sur r/haskell :).
Quoi qu'il en soit.
Je te souhaite encore une très bonne année 2021 !
Yann.
[fn:1] https://github.com/stuartsierra/component
[fn:2] https://github.com/puppetlabs/trapperkeeper