Haskell 为所有MonadTrans实例提供typeclass实例
我已经定义了自己的monad transformer: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
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具有不同的“提升”能力。这个方案试图解决这些困难,好吧。