整理 Monads - 将 monad 转换器的应用程序转换为新型 monad
Tidying up Monads - turning application of a monad transformer into newtype monad
我正在尝试采取例如ExceptT a (StateT A M)
,对于某些具体类型 A
和 monad M
,并将它们包装到我的新自定义 monad 中。
首先我发现 StateT A M
经常出现在其他上下文中,因此我决定最好将它单独包装在一个 monad M1
中,然后将 ExceptT a M1
包装到 M2
.
所需的 属性 是创建 MonadState
的 M1
和 M2
实例以及 M
的 class 实例(假设它称为 MyMonadClass
)。另外 M2
应该是 MonadError
.
的一个实例
首先我从简单类型的同义词开始:
type MyState = StateT A M
type MyBranch a = ExceptT a MyState
然后我想我会首先草拟实例声明(不实现实例),这就是我第一次遇到困难的地方。 instance MonadState A (MyState)
似乎不是正确的语法。我以为我必须先创建 newtype MyState' a = StateT a M
然后再创建 type MyState = MyState A
(在不需要的地方不要使用语言扩展)。
然而,一旦我开始将同义词转换为 newtype
声明,我就开始失去与 StateT A M
和 ExceptT ...
类型的联系。
newtype MyState' s a = MyState' { runMyState :: s -> (s, a) }
type MyState = MyState' A
newtype MyBranch e a = MyBranch { runMyBranch :: MyState (Either e a) }
现在已经实现的转换器消失了,我想我正在尝试做一些没有多大意义的事情。所以我的问题是:如何将这种行为正确地包装到新的复合单子中,使下面的层可以访问,从而避免不必要的提升并保持事情清晰和井井有条。
正常模式是为完整的转换器堆栈定义一个新类型。
data A = A
data E = E
newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
如果堆栈中有任何部分可以自行添加有意义的新功能,您也可以为这些部分定义新类型。
然后您使用 GeneralizedNewtypeDeriving
获取所有各种 monad classes 的实例。
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- base
import Control.Applicative
import Control.Monad
-- transformers
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.State.Lazy
-- mtl classes
import Control.Monad.Cont.Class
import Control.Monad.Error.Class
import Control.Monad.Reader.Class
import Control.Monad.State.Class
import Control.Monad.Writer.Class
data A = A
data E = E
newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
deriving (Functor, Applicative, Monad, MonadIO, -- classes from base and transformers
{- Alternative, MonadPlus, -} -- if E is a monoid
MonadState A, MonadError E, -- classes from mtl that MyBranchT provides
MonadCont, MonadReader r, MonadWriter w) -- classes from mtl that might be available from m
您必须手动编写 MonadTrans
实例。 lift
始终只是 lift
对于堆栈中的每个变压器一次。
instance MonadTrans MyBranchT where
lift = MyBranchT . lift . lift
如果堆栈中有任何部分可以自行添加有意义的新功能 X
,您还可以为这些功能定义一个新的 MonadX
class,写 MonadX
为每个 monad 转换器(StateT
、ExceptT
、ContT
等)创建实例,并为你的转换器堆栈派生 MonadX
实例(MyBranchT
).
您通常还会为 MyBranchT Identity
创建类型同义词,并为 runMyBranchT
和 runMyBranch
设置函数
import Data.Functor.Identity
type MyBranch a = MyBranchT Identity a
runMyBranchT :: MyBranchT m a -> A -> m (Either E a, A)
runMyBranchT mb s = runStateT (runExceptT (getMyBranchT mb)) s
runMyBranch :: MyBranch a -> A -> (Either E a, A)
runMyBranch mb s = runIdentity $ runMyBranchT mb s
我正在尝试采取例如ExceptT a (StateT A M)
,对于某些具体类型 A
和 monad M
,并将它们包装到我的新自定义 monad 中。
首先我发现 StateT A M
经常出现在其他上下文中,因此我决定最好将它单独包装在一个 monad M1
中,然后将 ExceptT a M1
包装到 M2
.
所需的 属性 是创建 MonadState
的 M1
和 M2
实例以及 M
的 class 实例(假设它称为 MyMonadClass
)。另外 M2
应该是 MonadError
.
首先我从简单类型的同义词开始:
type MyState = StateT A M
type MyBranch a = ExceptT a MyState
然后我想我会首先草拟实例声明(不实现实例),这就是我第一次遇到困难的地方。 instance MonadState A (MyState)
似乎不是正确的语法。我以为我必须先创建 newtype MyState' a = StateT a M
然后再创建 type MyState = MyState A
(在不需要的地方不要使用语言扩展)。
然而,一旦我开始将同义词转换为 newtype
声明,我就开始失去与 StateT A M
和 ExceptT ...
类型的联系。
newtype MyState' s a = MyState' { runMyState :: s -> (s, a) }
type MyState = MyState' A
newtype MyBranch e a = MyBranch { runMyBranch :: MyState (Either e a) }
现在已经实现的转换器消失了,我想我正在尝试做一些没有多大意义的事情。所以我的问题是:如何将这种行为正确地包装到新的复合单子中,使下面的层可以访问,从而避免不必要的提升并保持事情清晰和井井有条。
正常模式是为完整的转换器堆栈定义一个新类型。
data A = A
data E = E
newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
如果堆栈中有任何部分可以自行添加有意义的新功能,您也可以为这些部分定义新类型。
然后您使用 GeneralizedNewtypeDeriving
获取所有各种 monad classes 的实例。
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- base
import Control.Applicative
import Control.Monad
-- transformers
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.State.Lazy
-- mtl classes
import Control.Monad.Cont.Class
import Control.Monad.Error.Class
import Control.Monad.Reader.Class
import Control.Monad.State.Class
import Control.Monad.Writer.Class
data A = A
data E = E
newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
deriving (Functor, Applicative, Monad, MonadIO, -- classes from base and transformers
{- Alternative, MonadPlus, -} -- if E is a monoid
MonadState A, MonadError E, -- classes from mtl that MyBranchT provides
MonadCont, MonadReader r, MonadWriter w) -- classes from mtl that might be available from m
您必须手动编写 MonadTrans
实例。 lift
始终只是 lift
对于堆栈中的每个变压器一次。
instance MonadTrans MyBranchT where
lift = MyBranchT . lift . lift
如果堆栈中有任何部分可以自行添加有意义的新功能 X
,您还可以为这些功能定义一个新的 MonadX
class,写 MonadX
为每个 monad 转换器(StateT
、ExceptT
、ContT
等)创建实例,并为你的转换器堆栈派生 MonadX
实例(MyBranchT
).
您通常还会为 MyBranchT Identity
创建类型同义词,并为 runMyBranchT
和 runMyBranch
import Data.Functor.Identity
type MyBranch a = MyBranchT Identity a
runMyBranchT :: MyBranchT m a -> A -> m (Either E a, A)
runMyBranchT mb s = runStateT (runExceptT (getMyBranchT mb)) s
runMyBranch :: MyBranch a -> A -> (Either E a, A)
runMyBranch mb s = runIdentity $ runMyBranchT mb s