将 "deriving via" 与类型族一起使用

Using "deriving via" with a type family

我有一个带有默认实现的类型类,如果用户想要使用他们的自定义 monad,我想提供一种派生类型类的简单方法。

这是其他人提供给我的解决方案:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}

import Control.Monad.Cont (MonadIO, MonadTrans (lift))
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT))

----------------- My module's definitions -----------------

class Monad m => MonadFoo m where
  foo :: m ()

instance MonadFoo IO where
  foo = putStrLn "Hello world!"

instance MonadFoo m => MonadFoo (ReaderT r m) where
  foo = lift foo

------------------------------------------------------------

------ The user's custom monad + instance definitions ------

data AppEnv = AppEnv

newtype AppM a = AppM
  { runAppM :: ReaderT AppEnv IO a
  }
  deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)

deriving via (ReaderT AppEnv IO) instance MonadFoo AppM

------------------------------------------------------------

-- Example usage
program :: IO ()
program = runReaderT (runAppM foo) AppEnv
> program
"Hello world!"

如果我的类型类使用类型族,我将无法使用 deriving via。例如:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

import Control.Monad.Cont (MonadIO, MonadTrans (lift))
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT))

----------------- My module's definitions -----------------

class Monad m => MonadFoo ctx m where
  type FooCtx ctx
  foo :: m (FooCtx ctx)

data DummyCtx = DummyCtx

instance MonadFoo DummyCtx IO where
  type FooCtx DummyCtx = ()
  foo :: IO ()
  foo = putStrLn "hello"

instance MonadFoo DummyCtx m => MonadFoo DummyCtx (ReaderT r m) where
  type FooCtx DummyCtx = ()
  foo :: ReaderT r m ()
  foo = lift $ foo @DummyCtx

------------------------------------------------------------

------ The user's custom monad + instance definitions ------

data AppEnv = AppEnv

newtype AppM a = AppM
  { runAppM :: ReaderT AppEnv IO a
  }
  deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)

deriving via (ReaderT AppEnv IO) instance MonadFoo DummyCtx AppM

最后一行没有编译:

[typecheck] [E] • Can't make a derived instance of                                                                                       
~          ‘MonadFoo DummyCtx AppM’ with the via strategy:                                                                                      
~          the associated type ‘FooCtx’ is not parameterized over the last type                                                                 
~      variable                                                                                                                                 
~            of the class ‘MonadFoo’                                                                                                            
~      • In the stand-alone deriving instance for ‘MonadFoo DummyCtx AppM’

当类型类具有类型族时,如何让 deriving via 子句进行编译?

如错误消息所述,您的关联类型 FooCtx 仅取决于 ctx,而不取决于 m,因此可能会产生歧义,如下所示:

instance MonadFoo X A where
  type FooCtx X = Int
  ...

instance MonadFoo X B where
  type FooCtx X = String
  ...

现在,FooCtx X 的计算结果是 Int 还是 String 是不明确的。

要解决这个问题,只需将 m 添加到 FooCtx:

的参数中
class Monad m => MonadFoo ctx m where
  type FooCtx ctx m
  ...

instance MonadFoo DummyCtx IO where
  type FooCtx DummyCtx IO = ()
  ...

instance MonadFoo DummyCtx m => MonadFoo DummyCtx (ReaderT r m) where
  type FooCtx DummyCtx (ReaderT r m) = ()
  ...

(我想我会添加这个作为答案,因为它毕竟这么简单)