Haskell 为所有MonadTrans实例提供typeclass实例

Haskell 为所有MonadTrans实例提供typeclass实例,haskell,typeclass,monad-transformers,Haskell,Typeclass,Monad Transformers,我已经定义了自己的monad transformer: data Config = Config { ... } data State = State { ... } newtype FooT m a = FooT { runFoo :: ReaderT Config (StateT State m) a } deriving (Functor, Monad, MonadReader Config, MonadState State) 我为它定义了一个MonadTran

我已经定义了自己的monad transformer:

data Config = Config { ... }

data State = State { ... }

newtype FooT m a = FooT {
      runFoo :: ReaderT Config (StateT State m) a
    } deriving (Functor, Monad, MonadReader Config, MonadState State)
我为它定义了一个
MonadTrans
实例

instance MonadTrans FooT where
   lift = FooT . lift . lift
现在,我有各种各样的单子,我不能仅仅由编译器为我派生。我将以
MonadIO
为例。因此,我将我的
MonadIO
实例定义为

instance MonadIO m => MonadIO (FooT m) where
    liftIO = lift . liftIO
然而,我发现我为每个单子做了很多举重。为什么每个Monad typeclass(即MonadIO、MonadCatchIO、MonadFoo)的作者不能根据MonadTrans定义一个通用实例,而不是让我为我提出的每个新MonadTrans实现一个实例?洛杉矶

instance (MonadIO m, MonadTrans t, Monad (t m)) => MonadIO (t m) where
  liftIO = lift . liftIO
这需要编译
UndededicatableInstances
,我不确定它是否正确(事实上,我很确定它是不正确的),但现在可以表达我的意图


那么,这可能吗?若否,原因为何?会吗?

比方说,我想出了一个替代方案,叫做
MyMonadIO
。它在各个方面都像MonadIO,除了名字:

class Monad m => MyMonadIO m where
  myLiftIO :: IO a -> m a
假设您的
类型:

newtype FooT m a = FooT
  { runFoo :: ReaderT Config (StateT AppState m) a
  } deriving (Functor, Applicative, Monad, MonadReader Config, MonadState AppState)
可以为创建
MyMonadIO
的实例, ,最后是脚。我已经添加了额外的类型注释来实现它 让读者更容易了解发生了什么:

instance MyMonadIO m => MyMonadIO (ReaderT r m) where
  myLiftIO :: IO a -> ReaderT r m a
  myLiftIO = (lift :: m a -> ReaderT r m a) . (myLiftIO :: IO a -> m a)

instance MyMonadIO m => MyMonadIO (StateT s m) where
  myLiftIO :: IO a -> StateT s m a
  myLiftIO = (lift :: m a -> StateT s m a) . (myLiftIO :: IO a -> m a)

instance MyMonadIO m => MyMonadIO (FooT m) where
  myLiftIO :: IO a -> FooT m a
  myLiftIO = (lift :: m a -> FooT m a) . (myLiftIO :: IO a -> m a)
也可以使用它来轻松派生
MyMonadIO
用于
FooT
(假设已经有
ReaderT
的实例,并且
StateT
):


如果查看
ReaderT
StateT
myLiftIO
函数体, 和
FooT
实例,它们完全相同:
lift。myLiftIO

下面重复这个问题:

为什么每个Monad typeclass(即MonadIO、MonadCatchIO、, MonadFoo)不使用MonadTrans定义常规实例,而使用 让我为每个新的MonadTrans实现一个实例

对于MyMonadIO,此通用实例如下所示:

instance (Monad (t n), MyMonadIO n, MonadTrans t) => MyMonadIO (t n) where
  myLiftIO :: IO a -> t n a
  myLiftIO = (lift :: n a -> t n a) . (myLiftIO :: IO a -> n a)
定义了此实例后,
ReaderT
不需要特定实例,
StateT
,甚至
FooT

这需要
不可判定的实例
。但是,问题不是不可判定性,而是此实例与一些潜在有效的
MyMonadIO
实例重叠

例如,设想以下数据类型:

newtype FreeIO f a = FreeIO (IO (Either a (f (FreeIO f a))))

instance Functor f => Functor (FreeIO f) where
  fmap :: (a -> b) -> FreeIO f a -> FreeIO f b
  fmap f (FreeIO io) = FreeIO $ do
    eitherA <- io
    pure $
      case eitherA of
        Left a -> Left $ f a
        Right fFreeIO -> Right $ fmap f <$> fFreeIO

instance Functor f => Applicative (FreeIO f) where
  pure :: a -> FreeIO f a
  pure a = FreeIO . pure $ Left a

  (<*>) :: FreeIO f (a -> b) -> FreeIO f a -> FreeIO f b
  (<*>) (FreeIO ioA2b) (FreeIO ioA) = FreeIO $ do
    eitherFa2b <- ioA2b
    eitherFa <- ioA
    pure $
      case (eitherFa2b, eitherFa) of
        (Left a2b, Left a) -> Left $ a2b a
        (Left a2b, Right fFreeIOa) -> Right $ fmap a2b <$> fFreeIOa
        (Right fFreeIOa2b, o) -> Right $ (<*> FreeIO (pure o)) <$> fFreeIOa2b

instance Functor f => Monad (FreeIO f) where
  (>>=) :: FreeIO f a -> (a -> FreeIO f b) -> FreeIO f b
  (>>=) (FreeIO ioA) mA2b = FreeIO $ do
    eitherFa <- ioA
    case eitherFa of
      Left a ->
        let (FreeIO ioB) = mA2b a
        in ioB
      Right fFreeIOa -> pure . Right $ fmap (>>= mA2b) fFreeIOa
我们甚至可以想象使用
FreeIO
编写函数:

instance Functor f => MyMonadIO (FreeIO f) where
  myLiftIO :: IO a -> FreeIO f a
  myLiftIO ioA = FreeIO (Left <$> ioA)
tryMyLiftIOWithFreeIO :: Functor f => FreeIO f ()
tryMyLiftIOWithFreeIO = myLiftIO $ print "hello"
如果您尝试使用此实例(
MyMonadIO(FreeIO f)
)和上面的坏实例编译
tryMyLiftIOWithFreeIO
),则会出现以下错误:

test-monad-trans.hs:103:25: error:
    • Overlapping instances for MyMonadIO (FreeIO f)
        arising from a use of ‘myLiftIO’
      Matching instances:
        instance (Monad (t n), MyMonadIO n, MonadTrans t) => MyMonadIO (t n)
          -- Defined at test-monad-trans.hs:52:10
        instance Functor f => MyMonadIO (FreeIO f)
          -- Defined at test-monad-trans.hs:98:10
    • In the expression: myLiftIO $ print "hello"
      In an equation for ‘tryMyLiftIOWithFreeIO’:
          tryMyLiftIOWithFreeIO = myLiftIO $ print "hello"
为什么会发生这种情况

那么,在
实例中(Monad(tn),MyMonadIO n,MonadTrans t)=>MyMonadIO(tn)
,什么是
t
n

因为
n
应该是
单子
,所以它的种类是
*->*
。由于
t
是一个单变量转换器,它的类型是
(*->*)->*->*->*
tn
也应该是一个
Monad
,所以它的种类也是
*->*

n :: * -> *
t :: (* -> *) -> * -> *
t n :: * -> *
f :: * -> *
FreeIO :: (* -> *) -> * -> *
FreeIO f :: * -> *
现在,在
实例函子f=>MyMonadIO(FreeIO f)
中,
FreeIO
f
的类型是什么

f
应该是一个
函子,所以它的种类是
*->*
FreeIO
的种类是
(*->*)->*->*->*
FreeIO f
是一个
Monad
,所以它的种类是
*->*

n :: * -> *
t :: (* -> *) -> * -> *
t n :: * -> *
f :: * -> *
FreeIO :: (* -> *) -> * -> *
FreeIO f :: * -> *
由于种类相同,您可以看到
实例函子f=>MyMonadIO(FreeIO f)
实例(Monad(tn)、MyMonadIO n、MonadTrans t)=>MyMonadIO(tn)
重叠。GHC不确定该选哪一个

您可以通过将实例
FreeIO
实例标记为
OVERLAPPING
来解决此问题:

instance {-# OVERLAPPING #-} Functor f => MyMonadIO (FreeIO f) where
  myLiftIO :: IO a -> FreeIO f a
  myLiftIO m = FreeIO (Left <$> m)
这表示对于任何
tn
,都有
myLiftIO
的默认实现, 其中
n
MyMonadIO
的实例,
t
MonadTrans

对于
myLiftIO
的此默认符号,为
ReaderT
StateT
定义
MyMonadIO
的实例如下:

class Monad m => MyMonadIO m where
  myLiftIO :: IO a -> m a
  default myLiftIO
    :: forall t n a.
       ( MyMonadIO n
       , MonadTrans t
       , m ~ t n
       )
    => IO a -> t n a
  myLiftIO = (lift :: n a -> t n a) . (myLiftIO :: IO a -> n a)
instance MyMonadIO m => MyMonadIO (ReaderT r m)
instance MyMonadIO m => MyMonadIO (StateT s m)
很简单。您不需要提供myLiftIO的函数体,因为 它将使用默认值

唯一的缺点是它没有被广泛采用。这个
DefaultSignatures
机器似乎主要用于,而不是monad类型类。

我还没有完全理解这个主题,但问题似乎是不同的monad具有不同的“提升”能力。这个方案试图解决这些困难,好吧。