Quelqu’un at-il déjà rencontré un transformateur Monad dans la nature?

Dans mon domaine d’affaires – back office informatique pour une institution financière – il est très courant qu’un composant logiciel contienne une configuration globale, enregistre ses progrès, ait une sorte de gestion des erreurs / de court-circuit de calcul… peut être modélisé par Reader-, Writer-, Maybe-monads et autres dans Haskell et composé avec des transformateurs monad.

Mais il semble y avoir des inconvénients: le concept derrière les transformateurs de monade est assez complexe et difficile à comprendre, les transformateurs de monade conduisent à des signatures de type très complexes et ils entraînent des pénalités de performance.

Alors, je me demande: est-ce que les transformateurs de monade sont les meilleures pratiques en ce qui concerne les tâches courantes mentionnées ci-dessus?

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

  • John Hughes rapporte qu’il trouve plus facile d’enseigner les transformateurs de monade que d’enseigner les monades, et que ses élèves réussissent mieux avec une approche «transformateurs d’abord».

  • Les développeurs du GHC évitent généralement les transformateurs monades, préférant enrouler leurs propres monades qui intègrent toutes les fonctionnalités dont ils ont besoin. (On m’a dit aujourd’hui, en termes non équivoques, que GHC n’utilisera pas un transformateur de monade défini il y a trois jours.)

Pour moi, les transformateurs monades ressemblent beaucoup à la programmation sans points (c.-à-d. La programmation sans variables nommées), ce qui est logique; après tout, ils programment exactement sans point au niveau du type. Je n’ai jamais aimé la programmation sans points car il est utile de pouvoir introduire le nom occasionnel.

Ce que j’observe dans la pratique est

  • Le nombre de transformateurs monad disponibles sur Hackage est très élevé et la plupart d’entre eux sont assez simples. Ceci est une instance classique du problème où il est plus difficile d’apprendre une grande bibliothèque que de déployer vos propres instances.

  • Les monades telles que Writer, State et Environment sont si simples que je ne vois pas beaucoup d’avantages aux transformateurs monad.

  • Les transformateurs monad brillent par leur modularité et leur réutilisation. Cette propriété est magnifiquement illustrée par Liang, Hudak et Jones dans leur journal phare “Monad Transformers and Modular Interpreters” .

Les transformateurs monad sont-ils les meilleures pratiques en ce qui concerne les tâches courantes mentionnées ci-dessus?

Je dirais pas. Où les transformateurs monad sont les meilleures pratiques est l’endroit où vous avez une gamme de produits associés que vous pouvez créer en composant et en réutilisant les transformateurs monad de différentes manières. Dans un cas comme celui-ci, vous développez probablement un certain nombre de transformateurs monad qui sont importants pour votre domaine problématique (comme celui qui a été rejeté pour GHC) et vous (a) les composez de plusieurs manières; (b) atteindre une quantité significative de réutilisation pour la plupart des transformateurs; (c) encapsulent quelque chose de non sortingvial dans chaque transformateur monad.

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

Le concept derrière les transformateurs de monade est assez complexe et difficile à comprendre, les transformateurs monades conduisent à des signatures de type très complexes

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

  • Utiliser une stack Monad particulière d’un transformateur n’est pas plus difficile à utiliser qu’une simple Monad. Il suffit de penser aux calques \ stacks et tout ira bien. Vous n’avez presque jamais besoin de soulever une fonction pure (ou une action IO spécifique) plus d’une fois.
  • Comme mentionné précédemment, cachez votre stack Monad dans un newtype, utilisez dériver généralisé et masquez le constructeur de données dans un module.
  • Essayez de ne pas utiliser une stack Monad particulière dans la signature de type de fonction, écrivez du code général avec des classes de type Monad telles que MonadIO, MonadReader et MonadState (utilisez l’extension de contextes flexibles standardisée dans Haskell 2010).
  • Utilisez des bibliothèques comme fclabels pour réduire les actions standard qui accèdent à des parties de l’enregistrement dans une Monade.

Les transformateurs Monad ne sont pas vos seules options, vous pouvez écrire une Monad personnalisée, utiliser une suite Monad. Vous avez des références / tableaux mutables dans IO (global), ST (local et contrôlé, pas d’actions sur IO), MVar (synchronisation), TVar (transactionnel).

J’ai entendu dire que les problèmes d’efficacité potentiels avec les transformateurs Monad pourraient être atténués en ajoutant simplement des pragmas INLINE pour lier / retourner dans la source de la bibliothèque mtl / transformers.

J’ai récemment “chuté” sur la composition monade dans le contexte de F #. J’ai écrit une DSL qui s’appuie fortement sur la monade d’état: Tous les composants reposent sur la monade d’état: l’parsingur (monade parsingur basé sur la monade d’état), tables de correspondance de variables (plusieurs types internes), tables de correspondance d’identifiant. Et comme ces composants fonctionnent tous ensemble, ils reposent sur la même monade d’état. Il y a donc une notion de composition d’état qui regroupe les différents états locaux, et la notion d’accesseur d’état qui donne à chaque algo sa propre visibilité.

Au départ, le design était vraiment “juste une grande monade”. Mais alors j’ai commencé à avoir besoin d’états avec seulement des durées de vie locales, et pourtant toujours dans le contexte de l’état “persistant” (et encore une fois, tous ces états sont gérés par des monades d’état). Pour cela, j’ai eu besoin d’introduire des transformateurs de monade d’état qui augmentent l’état et adaptent les monades d’état ensemble. J’ai également ajouté un transformateur pour passer librement entre une monade d’état et 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 monade existent dans le “sauvage”. Pourtant, je suis fermement opposé à leur utilisation “out of the box”. Écrivez votre application avec des blocs de construction simples, en utilisant de petits ponts fabriqués à la main entre vos modules, si vous finissez par utiliser quelque chose comme un transformateur monad, c’est génial; Ne partez pas de là.

Et à propos des signatures de type: je suis venu à penser que ce type de programmation ressemblait beaucoup à jouer aux échecs avec les yeux bandés (et je ne suis pas un joueur d’échecs): votre niveau de compétence doit être et types s’emboîtant. Les signatures de type finissent généralement par être une distraction, à moins que vous ne vouliez explicitement append des contraintes de type pour des raisons de sécurité (ou parce que le compilateur vous oblige à les donner avec des enregistrements F #).

Je pense que c’est une idée fausse, seule la monade IO n’est pas pure. les monades comme les monades Write / T / Reader / T / State / T / ST sont encore fonctionnelles.

Il me semble plus qu’une seule notion du terme pur / non pur. Votre définition “IO = impure, tout le rest = pur” ressemble à ce dont parle Peyton-Jones dans “Taming effects” ( http://ulf.wiger.net/weblog/2008/02/29/peyton-jones-taming). -effects-the-next-big-challenge / ). D’un autre côté, le monde réel Haskell (dans les dernières pages du chapitre Transformateur Monad) oppose des fonctions pures à des fonctions monadiques en général, faisant valoir que vous avez besoin de bibliothèques différentes pour les deux mondes. BTW, on pourrait argumenter que IO est pur aussi bien, ses effets secondaires étant encapsulés dans une fonction State avec le type RealWorld -> (a, RealWorld) . Après tout, Haskell se dit un langage purement fonctionnel (IO inclus, je présume :-).)

Ma question n’est pas tant sur ce qui peut être fait théoriquement, mais plus sur ce qui a été prouvé utile du sharepoint vue du génie logiciel. Les transformateurs Monad permettent la modularité des effets (et des abstractions en général), mais est-ce que la direction de la programmation devrait être dirigée vers?

À l’époque où j’apprenais les monades, j’ai créé une application utilisant une stack de StateT ContT IO pour créer une bibliothèque de simulation d’événements discrets. Les suites ont été utilisées pour stocker les threads monadiques, StateT contenant la queue des threads exécutables et les autres files d’attente utilisées pour les threads en attente de divers événements. Cela a très bien fonctionné. Je ne pouvais pas comprendre comment écrire l’instance de Monad pour un wrapper de type newtype, alors je l’ai juste fait un synonyme de type et cela a plutôt bien fonctionné.

Ces jours-ci, j’aurais probablement roulé ma propre monade à partir de rien. Cependant, chaque fois que je fais cela, je me retrouve à regarder “All About Monads” et la source de la MTL pour me rappeler à quoi ressemblent les opérations de liaison. En un sens, je pense toujours à une stack MTL même si le résultat est une monade personnalisée.

Donc, quelque chose qui a tendance à être plutôt global comme un journal ou une configuration, vous proposeriez de mettre la monade IO? En regardant des exemples (certes très limités) d’exemples, je commence à penser que le code de Haskell a tendance à être pur (c’est-à-dire pas du tout monadique) ou à la monade IO. Ou est-ce une idée fausse?

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

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

Tout ce que vous faites, c’est impliquer l’état, ce que vous feriez vous-même à la main, la notation de do vous donne juste un bon sucre syntaxique pour le rendre impératif. Vous ne pouvez faire aucune action IO ici, vous ne pouvez appeler aucune fonction étrangère. ST monad vous permet d’avoir de véritables références mutables dans une scope locale tout en ayant une interface de fonction pure et, vous ne pouvez pas y faire d’actions IO, cela rest purement fonctionnel.

Vous ne pouvez pas éviter certaines actions d’IO, mais vous ne voulez pas vous rabattre sur IO car c’est là que tout peut aller, des missiles peuvent être lancés, vous n’avez aucun contrôle. Haskell a des abstractions pour contrôler des calculs efficaces à divers degrés de sécurité / pureté, IO monad 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 monad ou d’une monade faite sur mesure qui fait la même chose que de les composer avec des transformateurs. Je n’ai jamais écrit de monade personnalisée (pour le moment) mais j’ai utilisé pas mal de transformateurs monad (mon propre code, pas au travail), ne vous inquiétez pas trop, utilisez-les et ce n’est pas aussi grave que vous le pensez .

Avez-vous vu le chapitre de Real World Haskell qui utilise des transformateurs monad ?