52 votes

Quelqu'un a-t-il déjà rencontré un Transformateur de Monade dans la nature ?

Dans mon domaine d'activité - le back-office informatique d'une institution financière - il est très courant qu'un composant logiciel transporte une configuration globale, qu'il enregistre sa progression, qu'il ait une sorte de court-circuit de traitement des erreurs/calculs... Autant de choses qui peuvent être joliment modélisées par des monades Reader, Writer, Maybe et autres en Haskell et composées avec des transformateurs de monades.

Mais il semble y avoir quelques inconvénients : Le concept derrière les transformateurs de monades est assez délicat et difficile à comprendre, les transformateurs de monades conduisent à des signatures de types très complexes, et ils infligent une certaine pénalité de performance.

Alors je me demande : Les transformateurs de monades sont-ils la meilleure pratique pour traiter les tâches courantes mentionnées ci-dessus ?

43voto

Norman Ramsey Points 115730

La communauté Haskell est divisée sur cette question.

  • John Hughes rapporte qu'il trouve plus facile d'enseigner les transformateurs de monades que d'enseigner les monades, et que ses étudiants réussissent mieux avec une approche "transformateurs d'abord".

  • Les développeurs de GHC évitent généralement les transformateurs de monades, préférant élaborer leurs propres monades qui intègrent toutes les fonctionnalités dont ils ont besoin. (On m'a dit aujourd'hui même, en des termes très clairs, que GHC va pas utiliser un transformateur de monade que j'ai défini il y a trois jours).

Pour moi, les transformateurs de monades ressemblent beaucoup à la programmation sans points (c'est-à-dire la programmation sans variables nommées), ce qui est logique ; après tout, il s'agit exactement de programmation sans points au niveau des types. Je n'ai jamais aimé la programmation sans point, car il est utile de pouvoir introduire un nom occasionnel.

Ce que j'observe dans la pratique est

  • Le nombre de transformateurs de monades disponibles sur Hackage est très important, et la plupart d'entre eux sont assez simples. C'est un exemple classique du problème où il est plus difficile d'apprendre une grande bibliothèque que de créer ses propres instances.

  • Les monades comme Writer, State et Environment sont si simples que je ne vois pas l'intérêt des transformateurs de monades.

  • Les transformateurs de monades brillent par leur modularité et leur réutilisation. Cette propriété a été magnifiquement démontrée par Liang, Hudak et Jones dans leur article de référence intitulé "Transformateurs de monades et interprètes modulaires" .

Les transformateurs de monades sont-ils la meilleure pratique pour traiter les tâches courantes mentionnées ci-dessus ?

Je dirais pas. Où les transformateurs de monades sont La meilleure pratique est celle où vous avez gamme de produits d'abstractions connexes que vous pouvez créer en composant et en réutilisant les transformateurs de monades de différentes manières. Dans un cas comme celui-ci, vous développez probablement un certain nombre de transformateurs de monades qui sont importants pour votre domaine de problèmes (comme celui qui a été rejeté pour GHC) et vous (a) les composez de plusieurs façons ; (b) réutilisez de manière significative la plupart des transformateurs ; (c) encapsulez quelque chose de non trivial dans chaque transformateur de monade.

Mon transformateur de monade qui a été rejeté pour GHC ne répondait à aucun des critères (a)/(b)/(c) ci-dessus.

8voto

snk_kid Points 2234

Le concept derrière les transformateurs de monades est assez délicat et difficile à comprendre, les transformateurs de monades conduisent à signatures de type très complexes

Je pense que c'est un peu exagéré :

  • L'utilisation d'une pile de Monad particulière d'un transformateur n'est pas plus difficile à utiliser qu'un Monad ordinaire. Il suffit de penser aux couches \stacks et tu iras bien. Vous n'avez presque jamais besoin de lever une fonction pure (ou une action IO spécifique) plus d'une fois.
  • Comme nous l'avons déjà mentionné, cachez votre pile de monades dans un nouveau type, utilisez un dérivateur généralisé et cachez le constructeur de données dans un module.
  • Essayez de ne pas utiliser une pile Monad particulière dans la signature du type de fonction, écrivez du code général avec des classes de type Monad comme MonadIO, MonadReader et MonadState (utilisez l'extension de contextes flexibles qui est normalisée dans Haskell 2010).
  • Utilisez des bibliothèques comme fclabels pour réduire les actions passe-partout qui accèdent à des parties d'enregistrement dans un Monad.

Les transformateurs de monades ne sont pas vos seules options, vous pouvez écrire une monade personnalisée, utiliser une monade de continuation. Vous avez des références/réseaux mutables dans IO (global), ST (local et contrôlé, pas d'actions IO), MVar (synchronisation), TVar (transactionnel).

J'ai entendu dire que les problèmes potentiels d'efficacité des transformateurs Monad pourraient être atténués simplement en ajoutant des pragmas INLINE à bind/return dans le source de la bibliothèque mtl/transformers.

3voto

user519985 Points 51

Je suis récemment "tombé" sur la composition de monades dans le contexte de F#. J'ai écrit un DSL qui s'appuie fortement sur le state monad : Tous les composants s'appuient sur la monade d'état : l'analyseur (monade d'analyseur basée sur la monade d'état), les tables de correspondance des variables (plus d'une pour les types internes), les tables de recherche d'identifiants. Et comme ces composants fonctionnent tous ensemble, ils reposent sur la même monade d'état. Il existe donc une notion de composition d'état qui rassemble les différents états locaux, et une notion d'accesseurs d'état qui donne à chaque algo sa propre visibilité d'état.

Au départ, la conception était vraiment "juste une grosse monade d'état". Mais ensuite, j'ai commencé à avoir besoin d'états n'ayant qu'une durée de vie locale, et pourtant toujours dans le contexte de l'état "persistant" (et encore, tous ces états sont gérés par des monades d'état). Pour cela, j'ai dû introduire des transformateurs de monades d'état qui augmentent l'état et adaptent les monades d'état ensemble. J'ai également ajouté un transformateur pour passer librement d'une monade d'état à une monade d'état de continuation, mais je n'ai pas pris la peine de l'utiliser.

Par conséquent, pour répondre à la question : oui, les transformateurs de monades existent dans la "nature". Pourtant, je ne suis pas du tout favorable à leur utilisation "à l'état brut". Écrivez votre application avec des blocs de construction simples, en utilisant de petits ponts artisanaux entre vos modules. Si vous finissez par utiliser quelque chose comme un transformateur de monade, c'est très bien ; ne partez pas de là.

Et à propos des signatures de type : J'en suis venu à considérer ce type de programmation comme quelque chose de très similaire à jouer aux échecs les yeux bandés (et je ne suis pas un joueur d'échecs) : votre niveau de compétence doit être tel que vous "voyez" vos fonctions et vos types s'emboîter. Les signatures de type finissent par être une distraction, à moins que vous ne vouliez explicitement ajouter des contraintes de type pour des raisons de sécurité (ou parce que le compilateur vous oblige à les donner, comme avec les enregistrements F#).

2voto

snk_kid Points 2234

Donc quelque chose qui a tendance à être plutôt global comme un journal ou une configuration, vous suggéreriez de le mettre dans le IO ? En examinant un ensemble d'exemples (certes d'exemples (certes très limités), j'en viens à penser que le code Haskell a tendance à être soit pur (c'est-à-dire pas du tout monadique) soit dans la monade IO. Ou s'agit-il d'une idée fausse ?

Je pense que c'est une idée fausse, seule la monade IO n'est pas pure. Les monades comme Write/T/Reader/T/State/T/ST sont toujours purement fonctionnelles. Vous pouvez écrire une fonction pure qui utilise n'importe laquelle de ces monades en interne, comme dans cet exemple complètement inutile :

foo :: Int -> Int
foo seed = flip execState seed $ do
    modify $ (+) 3
    modify $ (+) 4
    modify $ (-) 2

Tout ce que cela fait, c'est enfiler/plomber l'état implicitement, ce que vous feriez vous-même à la main explicitement, la do-notation ici vous donne juste un peu de sucre syntaxique pour que cela semble impératif. Vous ne pouvez pas faire d'actions IO ici, vous ne pouvez pas appeler de fonctions étrangères. La monade ST vous permet d'avoir de vraies références mutables dans une portée locale tout en ayant une interface de fonction pure et, vous ne pouvez pas faire d'actions d'E/S ici, c'est toujours purement fonctionnel.

Vous ne pouvez pas éviter certaines actions de l'OI, mais vous ne voulez pas vous rabattre sur l'OI pour tout, car c'est là que tout peut arriver, des missiles peuvent être lancés, vous n'avez aucun contrôle. Haskell a des abstractions pour contrôler les calculs efficaces à différents degrés de sécurité/pureté, la monade IO devrait être le dernier recours (mais vous ne pouvez pas l'éviter complètement).

Dans votre exemple, je pense que vous devriez vous en tenir à l'utilisation de transformateurs de monade ou d'une monade personnalisée qui fait la même chose que la composition avec des transformateurs. Je n'ai jamais écrit de monade personnalisée (pour l'instant) mais j'ai beaucoup utilisé les transformateurs de monade (dans mon propre code, pas au travail), ne vous inquiétez pas trop à leur sujet, utilisez-les et ce n'est pas aussi mauvais que vous le pensez.

Avez-vous vu le chapitre de Haskell dans le monde réel qui utilise des transformateurs de monades ?

2voto

martingw Points 1199

Je pense que c'est une fausse idée, seulement la monade IO n'est pas pure. Les monades comme monades Write/T/Reader/T/State/T/ST sont purement fonctionnelles.

Il me semble qu'il y a plus d'une notion sur le terme pur / non-pur. Votre définition "IO = non pur, tout le reste = pur" ressemble à ce dont Peyton-Jones parle dans "Taming effects" ( http://ulf.wiger.net/weblog/2008/02/29/peyton-jones-taming-effects-the-next-big-challenge/ ). D'autre part, le Real World Haskell (dans les dernières pages du chapitre Monad Transformer) oppose les fonctions pures aux fonctions monadiques en général - en argumentant que vous avez besoin de bibliothèques différentes pour les deux mondes. BTW, on pourrait argumenter que IO est également pur, ses effets secondaires étant encapsulés dans une fonction State de type Monde réel -> (a, Monde réel) . Après tout, Haskell se dit être un langage purement fonctionnel (IO inclus, je présume :-)).

Ma question ne porte pas tant sur ce qui peut être fait en théorie, mais plutôt sur ce qui s'est avéré utile du point de vue du génie logiciel. Les transformateurs de monades permettent la modularité des effets (et des abstractions en général), mais est-ce la direction de la programmation ? devrait vers lequel vous vous dirigez ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X