【问题标题】:Using "deriving via" with a type family对类型族使用“派生方式”
【发布时间】: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


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 2013-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多