Du currying au dynamic scope : fils conducteurs de l’injection implicite
L’application partielle et le currying transforment une fonction à plusieurs arguments en une chaîne de fonctions à un argument.
Une fonction devient : on peut fournir les arguments un par un, obtenant à chaque étape une fonction qui attend le reste.
Cette technique, apparemment anodine, permet de « préconfigurer » une fonction avec certains paramètres et de passer la fonction résultante à du code qui ne connaît pas ces paramètres. Le contexte (configuration, dépendances, environnement) est capturé dans la fermeture (closure), invisible pour l’appelant qui ne fournit que les arguments spécifiques à son cas.
C’est une forme primitive mais puissante d’injection de dépendances.
La Reader monad
La Reader monad systématise cette intuition.
Un Reader<E, A> est essentiellement une fonction habillée pour composer monadiquement. Le type E représente un environnement (configuration, services, contexte) que le programme lira sans le modifier.
On construit des computations qui dépendent de cet environnement sans jamais le mentionner explicitement dans les signatures intermédiaires ; seule l’exécution finale, via runReader(env), fournit la valeur concrète.
Les fonctions intermédiaires ignorent d’où vient l’environnement : elles savent seulement qu’il sera disponible. Cette propagation implicite élimine le parameter drilling où chaque couche doit explicitement transmettre le contexte à la suivante.
L’objet comme fermeture
La programmation orientée objet résout le même problème différemment avec le contexte this d’un objet.
Un constructeur capture les dépendances (injectées ou instanciées) et les stocke dans l’instance. Chaque méthode accède ensuite à ces dépendances via this.dependency sans les recevoir en paramètre.
L’objet est une fermeture :
- Le constructeur joue le rôle de l’application partielle initiale
thisjoue le rôle de l’environnement capturé
La différence réside dans la mutabilité potentielle et dans la liaison dynamique de this, source de bugs célèbres en JavaScript par exemple. Mais fondamentalement, constructeur et Reader répondent à la même question : comment propager un contexte sans l’expliciter à chaque appel.
React Context
React Context transpose ce pattern au monde des composants.
Un Provider établit une valeur contextuelle en haut de l’arbre ; les composants descendants y accèdent via useContext sans que les composants intermédiaires ne la transmettent explicitement par props (le fameux props drilling).
C’est exactement la sémantique du Reader : une valeur environnementale disponible implicitement pour toute la sous-arborescence.
React pousse plus loin avec la possibilité de shadower le contexte (un Provider imbriqué redéfinit la valeur pour sa sous-arborescence), rejoignant ainsi les capacités du dynamic scope où la liaison d’une variable dépend de la pile d’appels plutôt que de la structure lexicale.
Dynamic scope contrôlé
Ces mécanismes (currying, Reader, this, Context) émulent tous une forme de dynamic scope contrôlé dans des langages à scope lexical, qui sont les plus courants aujourd’hui.
Le dynamic scope pur, où une variable se résout en remontant la pile d’appels, est notoirement difficile à raisonner et a été largement abandonné. Cela étant, le besoin qu’il adressait (accéder à un contexte ambiant sans le passer explicitement) reste réel.
Les solutions modernes réintroduisent ce pouvoir de manière disciplinée :
- Le Reader rend le type de l’environnement explicite
- React Context limite la propagation à l’arbre de composants
- L’injection de dépendances par constructeur la limite à l’instance
Chacune offre l’ergonomie du dynamic scope (l’absence de parameter drilling) avec les garanties de traçabilité du lexical scope.
Envie d'approfondir ces sujets ?
Nous aidons les équipes à adopter ces pratiques via du conseil et de la formation.
ou écrivez-nous à contact@evryg.com