49 votes

Éviter l'ascenseur avec les transformateurs Monad

J'ai un problème qui convient très bien en utilisant une pile de MT (ou même un MT) sur IO. Tout est bon sauf qu'utiliser un ascenseur avant chaque action est terriblement ennuyant! Je soupçonne qu'il n'y a vraiment rien à faire à ce sujet, mais je pensais demander de toute façon.

Je suis conscient de lever des blocs entiers, mais que se passe-t-il si le code est vraiment mixte? Ne serait-il pas intéressant que GHC ajoute du sucre syntaxique (par exemple, <-$ = <- lift )?

56voto

ehird Points 30215

Pour tous les standard de mtl monades, vous n'avez pas besoin d' lift à tous. get, put, ask, tell - qu'ils travaillent tous dans toute monade avec le droit transformateur quelque part dans la pile. La pièce manquante est - IO, et même là, liftIO ascenseurs arbitraire IO action un nombre arbitraire de couches.

C'est fait avec typeclasses pour chaque "effet" sur l'offre: par exemple, MonadState fournit get et put. Si vous voulez créer votre propre newtype wrapper autour d'un transformateur de la pile, vous pouvez le faire deriving (..., MonadState MyState, ...) avec l' GeneralizedNewtypeDeriving d'extension ou de rouler votre propre instance:

instance MonadState MyState MyMonad where
  get = MyMonad get
  put s = MyMonad (put s)

Vous pouvez l'utiliser pour exposer de manière sélective ou masquer des composants de votre combiné transformateur, en définissant certains cas et pas dans d'autres.

(Vous pouvez facilement étendre cette approche à un tout nouveau monadique effets que vous définissez vous-même, en définissant votre propre typeclass et de fournir réutilisable cas pour les transformateurs standard, mais tout nouveau monades sont rares; la plupart du temps, vous aurez simplement à composer l'assortiment standard offert par mtl.)

49voto

dflemstr Points 18999

Vous pouvez faire vos fonctions monade-agnostique en utilisant typeclasses au lieu de béton monade des piles.

Disons que vous avez cette fonction, par exemple:

bangMe :: State String ()
bangMe = do
  str <- get
  put $ str ++ "!"
  -- or just modify (++"!")

Bien sûr, vous vous rendez compte qu'il fonctionne comme un transformateur ainsi, on pourrait écrire:

bangMe :: Monad m => StateT String m ()

Toutefois, si vous avez une fonction qui utilise une autre pile, disons - ReaderT [String] (StateT String IO) () ou que ce soit, vous devrez utiliser le redoutable lift fonction! Alors comment est-ce que évitée?

L'astuce est de faire de la signature de la fonction même plus générique, de sorte qu'il est dit que l' State monade peut apparaître n'importe où dans la monade de la pile. Ceci est fait comme ceci:

bangMe :: MonadState String m => m ()

Cela oblige m à être une monade qui prend en charge l'état (quasi) n'importe où dans la monade de la pile, et la fonction va donc travailler sans avoir à le soulever pour une telle pile.

Il y a un problème; depuis IO n'est pas une partie de l' mtl, il n'a pas, d'un transformateur (par exemple, IOT), ni d'une pratique de type de classe par défaut. Alors, que devez-vous faire lorsque vous voulez soulever IO actions de manière arbitraire?

Le sauvetage est MonadIO! Il se comporte presque identique à MonadState, MonadReader etc, la seule différence étant qu'il est un peu différent d'un mécanisme de levage. Il fonctionne comme ceci: vous pouvez prendre n'importe quel IO d'action, et d'utiliser liftIO pour la transformer en une monade agnostique version. Donc:

action :: IO ()
liftIO action :: MonadIO m => m ()

Par la transformation de l'ensemble de la monadique actions que vous souhaitez utiliser de cette manière, vous pouvez, s'entrelacent monades autant que vous le souhaitez sans aucune fastidieux de levage.

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