1
votes

Related to MonadTransControl instance for a custom monad where, unfortunately (but understandably) the author gave up on monad-control. However, that is not an option for me, because I need to use Control.Concurrent.Async.Lifted within my custom monad:

{-# LANGUAGE DataKinds, GADTs, ScopedTypeVariables #-}

data FeatureFlag = Feature1 | Feature2

newtype AppM (features :: [FeatureFlag]) a = AppM (ReaderT Env IO a) 
  deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO, MonadThrow, MonadCatch, MonadMask, MonadUnliftIO)

I've tried reading https://www.stackage.org/haddock/lts-12.1/monad-control-1.0.2.3/Control-Monad-Trans-Control.html but nothing seems to be making sense to me. Conceptually I understand why MonadBaseControl and friends are required, thanks to the walk-through at https://www.yesodweb.com/book/monad-control , but I can't figure out how to implement this.

The official docs have an implementation guide at the very top, but it assumes monad-transformers:

Define instances MonadTransControl T for all transformers T, using the defaultLiftWith and defaultRestoreT functions on the constructor and deconstructor of T.

[...]

Define instances MonadBaseControl B m => MonadBaseControl B (T m) for all transformers:

How do I write a sensible instance for MonadBaseControl IO (AppM fs) where the fs part is preserved when the monad is unwrapped and wrapped again? Also, I'm assuming that I don't really have to implement MonadTransControl, because AppM fs is not a transformer.

PS: Also related - Is it safe to derive MonadThrow, MonadCatch, MonadBaseControl, MonadUnliftIO, etc?

1
What goes wrong if you just cargo-cult ReaderT's instance?Daniel Wagner

1 Answers

1
votes

Your AppM is basically a ReaderT transformer; you've just specialized it to IO. You could rewrite it as:

newtype AppT (features :: [FeatureFlag]) m a = AppT (ReaderT Env m a)

with a type alias for the IO version:

type AppM features = AppT features IO

Then you should be able to use a combination of usual deriving clauses and standalone deriving clauses to get the classes you want.

At least, the following typechecks, and GHC seems to think it's produced an instance MonadBaseControl IO (AppT features IO).

{-# LANGUAGE DataKinds, FlexibleInstances, GADTs, GeneralizedNewtypeDeriving,
    KindSignatures, MultiParamTypeClasses, ScopedTypeVariables, StandaloneDeriving,
    UndecidableInstances #-}

import Control.Monad.Base
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.IO.Unlift
import Control.Monad.Trans.Control

data FeatureFlag = Feature1 | Feature2

data Env

newtype AppT (features :: [FeatureFlag]) m a = AppT (ReaderT Env m a)
  deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO,
            MonadThrow, MonadCatch, MonadMask,
            MonadTrans, MonadTransControl)
type AppM features = AppT features IO
deriving instance MonadBase IO (AppM features)
deriving instance MonadBaseControl IO (AppM features)
deriving instance MonadUnliftIO (AppM features)