【发布时间】:2021-12-31 22:06:35
【问题描述】:
我有一个带有默认实现的类型类,如果用户想要使用他们的自定义 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!"
如果我的类型类使用类型族,我将无法使用派生方式。例如:
{-# 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 子句进行编译?
【问题讨论】:
-
为什么你的关联类型没有被
m参数化?有什么原因吗? -
我不确定你的意思,你能解释一下吗? FooCtx 用于将返回类型绑定到其 ctx,因此我可以在更改其返回类型时选择在调用站点使用哪个类型类实例
-
您能否编辑您的问题以显示(使用代码,而不仅仅是告诉)您实际使用关联类型的方式?
-
我的意思是
FooCtx仅依赖于ctx,而不依赖于m,从而产生了像instance MonadFoo X A where FooCtx X = Int; instance MonadFoo X B where FooCtx X = Bool这样的歧义的可能性 -
原来这确实是一个模棱两可的问题。一旦我将
m约束添加到FooCtx,编译器就能够解析该实例。谢谢@FyodorSoikin!
标签: haskell deriving derivingvia