2017-09-26 5 views
8

Je suis actuellement la construction d'une nouvelle API, et l'une des fonctions qu'il fournit actuellement est:Dois-je préférer MonadUnliftIO ou MonadMask pour les fonctions de bracketing?

inSpan :: Tracer -> Text -> IO a -> IO a 

Je cherche à déplacer que Tracer dans une monade, me donner une signature plus comme

inSpan :: MonadTracer m => Text -> m a -> m a 

La mise en œuvre de inSpan utilise bracket, ce qui signifie que j'ai deux options principales:

class MonadUnliftIO m => MonadTracer m 

ou

class MonadMask m => MonadTracer m 

Mais que dois-je préférer? Notez que je contrôle tous les types que j'ai mentionnés, ce qui me fait légèrement pencher vers MonadMask car il n'applique pas IO en bas (c'est-à-dire que nous pourrions avoir une instance MonadTracer pure).

Y a-t-il autre chose que je devrais considérer?

Répondre

19

Nous allons exposer les options d'abord (répéter une partie de votre question dans le processus):

  • MonadMask de la bibliothèque exceptions. Cela peut fonctionner sur une large gamme de monades et de transformateurs, et ne nécessite pas que la monade de base soit IO.
  • MonadUnliftIO de la bibliothèque unliftio-core (ou unliftio). Cette bibliothèque fonctionne uniquement pour les monades avec IO à leur base, et qui est en quelque sorte isomorphe à ReaderT env IO.
  • MonadBaseControl de la bibliothèque monad-control. Cette bibliothèque nécessitera IO à la base, mais permettra non-ReaderT.

Maintenant les compromis. MonadUnliftIO est le plus récent ajout à la mêlée, et a le support de bibliothèque le moins développé. Cela signifie que, en plus des limitations de ce que les monades peuvent être des instances, de nombreuses bonnes instances n'ont tout simplement pas encore été écrites.

La question importante est: pourquoi MonadUnliftIO rend cette exigence apparemment arbitraire autour de ReaderT-like things? C'est pour éviter les problèmes avec l'état monadique perdu. Par exemple, la sémantique de bracket_ (put 1) (put 2) (put 3) n'est pas vraiment claire, et par conséquent MonadUnliftIO n'autorise pas une instance StateT.

MonadBaseControl détend la restriction ReaderT et dispose d'un support de bibliothèque plus étendu. Il est également considéré plus compliqué en interne que les deux autres, mais pour vos usages, cela ne devrait pas vraiment avoir d'importance. Et cela vous permet de faire des erreurs avec l'état monadique comme mentionné ci-dessus. Si vous êtes prudent dans votre utilisation, cela n'aura pas d'importance.

MonadMask permet des piles de transformateurs totalement pures. Je pense qu'il y a un bon argument à tirer de l'utilité de modéliser des exceptions asynchrones dans une pile pure, mais je comprends que ce genre d'approche est quelque chose que les gens veulent parfois faire.En échange d'obtenir plus d'instances, vous avez toujours les limitations autour de l'état monadique, plus l'incapacité de lever certaines IO actions de contrôle, comme timeout ou forkIO.

Ma recommandation:

  • Si vous voulez faire correspondre la façon dont la plupart des gens sont en train de faire aujourd'hui des choses, il est probablement préférable de choisir MonadMask, il est la solution la plus bien adoptée.
  • Si vous voulez cet objectif, mais vous devez également faire un timeout ou withMVar ou quelque chose, utilisez MonadBaseControl.
  • Et si vous savez qu'il existe un ensemble spécifique de monades dont vous avez besoin de compatibilité, et que vous voulez que le temps de compilation vous garantisse de l'exactitude de votre code vis-à-vis de l'état monadique, utilisez MonadUnliftIO.