0

我有一个带有默认实现的类型类,如果用户想要使用他们的自定义 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’

当类型类具有类型族时,如何使派生通过子句进行编译?

4

1 回答 1

2

正如错误消息所说,您的关联类型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) = ()
  ...

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

于 2022-01-01T21:35:28.273 回答