我已经定义了我自己的monad变换器:
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
现在,我有各种各样的monad,我只能由编译器为我派生。我以MonadIO
为例。所以我已将MonadIO
实例定义为
instance MonadIO m => MonadIO (FooT m) where
liftIO = lift . liftIO
然而,我发现我为每个Monad做了很多提升。为什么每个Monad类型类的作者(即MonadIO,MonadCatchIO,MonadFoo)都没有根据MonadTrans定义一个通用实例,而不是让我为每个新的MonadTrans实现一个实例? a la
instance (MonadIO m, MonadTrans t, Monad (t m)) => MonadIO (t m) where
liftIO = lift . liftIO
这需要UndecidableInstances
进行编译,而且我不确定它是否正确(事实上,非常确定它是不正确的),但是现在用来表达我的意图
那么,这可能吗?如果没有,为什么不呢?会不会是?
答案 0 :(得分:-1)
让我们说我已经找到了MonadIO
的替代品,叫做
MyMonadIO
。除了名称之外,它在各个方面都像MonadIO
:
class Monad m => MyMonadIO m where
myLiftIO :: IO a -> m a
假设您的FooT
类型:
newtype FooT m a = FooT
{ runFoo :: ReaderT Config (StateT AppState m) a
} deriving (Functor, Applicative, Monad, MonadReader Config, MonadState AppState)
可以为ReaderT
创建MyMonadIO
的实例,
StateT
,最后是FooT
。我添加了额外的类型注释来实现它
读者更容易弄清楚发生了什么:
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)
也可以使用GeneralizedNewtypeDeriving
轻松推导出来
MyMonadIO
FooT
(假设已有ReaderT
和StateT
的实例
newtype FooT m a = FooT
{ runFoo :: ReaderT Config (StateT AppState m) a
} deriving (Functor, Applicative, Monad, MyMonadIO, MonadReader Config, MonadState AppState)
):
myLiftIO
如果您查看ReaderT
,StateT
的{{1}}函数的正文,
和FooT
个实例,它们完全相同:lift . myLiftIO
。
以下是问题的重复:
为什么每个Monad类型类的作者(即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
。
这需要UndecidableInstances
。但是,这个问题不是不可判定性,而是这个实例与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
数据类型(尤其是Functor
,Applicative
和Monad
实例。只知道这是一种有效的数据类型就足够了。
(如果您有兴趣,这只是一个free monad缠绕IO
。)
可以为MyMonadIO
编写FreeIO
个实例:
instance Functor f => MyMonadIO (FreeIO f) where
myLiftIO :: IO a -> FreeIO f a
myLiftIO ioA = FreeIO (Left <$> ioA)
我们甚至可以想象使用FreeIO
编写函数:
tryMyLiftIOWithFreeIO :: Functor f => FreeIO f ()
tryMyLiftIOWithFreeIO = myLiftIO $ print "hello"
如果您尝试使用此实例(tryMyLiftIOWithFreeIO
)和上面的错误实例编译MyMonadIO (FreeIO f)
,则会收到以下错误:
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"
为什么会这样?
那么,在instance (Monad (t n), MyMonadIO n, MonadTrans t) => MyMonadIO (t n)
中,t
和n
的类型是什么?
由于n
应该是Monad
,所以它的种类是* -> *
。由于t
是一个monad变换器,它的类型是(* -> *) -> * -> *
。 t n
也应该是Monad
,所以它的种类也是* -> *
:
n :: * -> *
t :: (* -> *) -> * -> *
t n :: * -> *
现在,在instance Functor f => MyMonadIO (FreeIO f)
中,FreeIO
和f
的种类是什么?
f
应该是Functor
,所以它的种类是* -> *
。 FreeIO
的种类是(* -> *) -> * -> *
。 FreeIO f
是Monad
,所以它是* -> *
:
f :: * -> *
FreeIO :: (* -> *) -> * -> *
FreeIO f :: * -> *
由于种类相同,因此您可以看到instance Functor f => MyMonadIO (FreeIO f)
与instance (Monad (t n), MyMonadIO n, MonadTrans t) => MyMonadIO (t n)
重叠。 GHC不确定选哪一个!
您可以将实例FreeIO
实例标记为OVERLAPPING
:
instance {-# OVERLAPPING #-} Functor f => MyMonadIO (FreeIO f) where
myLiftIO :: IO a -> FreeIO f a
myLiftIO m = FreeIO (Left <$> m)
然而,这是一条堕落的危险路线。您可以在GHC user guide找到更多关于重叠可能不好的原因。
此FreeIO
示例由Edward Kmett创建。您可以在this reddit post中找到另一个重叠实例的聪明示例。
如果您打算编写monad类型类(如MyMonadIO
)和
将它发布到Hackage,一种选择是使用
DefaultSignatures
功能。这使您的库用户更容易定义
实例
使用DefaultSignatures
,定义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)
这表示任何myLiftIO
都有t n
的默认实施,
其中n
是MyMonadIO
的实例,而t
是其实例
MonadTrans
。
使用此myLiftIO
的默认模式,为MyMonadIO
和ReaderT
定义StateT
的实例将如下所示:
instance MyMonadIO m => MyMonadIO (ReaderT r m)
instance MyMonadIO m => MyMonadIO (StateT s m)
很简单。从那以后,您不需要提供myLiftIO
的函数体
它将使用默认值。
唯一的缺点是它没有广泛完成。该
DefaultSignatures
机制似乎主要用于generic
programming,而不是monad类型。