整理Monad - 将monad变换器的应用转换为newtype monad

jak*_*iel 9 haskell state-monad monad-transformers newtype lifting

我试图采取例如ExceptT a (StateT A M),对于一些具体类型A和monad M,并将它们包装到我的新自定义monad中.

首先,我确定了StateT A M在其他环境中经常出现,因此我决定这将是最好的单独包装在一个单子M1,然后包装ExceptT a M1M2.

所需的属性是make M1和的M2实例MonadState和类M(假设它被调用MyMonadClass).也M2应该是一个实例MonadError.

首先我从简单类型的同义词开始:

type MyState    = StateT A M
type MyBranch a = ExceptT a MyState
Run Code Online (Sandbox Code Playgroud)

然后我想我会首先勾画实例声明(没有实现实例),这就是我第一次被卡住的地方.instance MonadState A (MyState)似乎不是正确的语法.我以为我必须创建newtype MyState' a = StateT a M然后type MyState = MyState A(不要在没有必要的地方使用语言扩展).

然而,一旦我开始将同义词转换为newtype声明,我开始失去与StateT A MExceptT ...类型的连接.

newtype MyState' s a = MyState' { runMyState :: s -> (s, a) }
type MyState = MyState' A
newtype MyBranch e a = MyBranch { runMyBranch :: MyState (Either e a) }
Run Code Online (Sandbox Code Playgroud)

现在已经实施的变形金刚消失了,我想我正在尝试做一些没有多大意义的事情.所以我的问题是:如何将这种行为正确地包装到新的复合monad中,使得可以访问下面的层,以避免不必要的提升并保持清晰和井井有条.

Cir*_*dec 10

正常模式是为完整的变换器堆栈定义新类型.

data A = A
data E = E

newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a }
Run Code Online (Sandbox Code Playgroud)

如果堆栈中有任何部分可以自己添加有意义的新功能,那么您还可以为这些部分定义新类型.

然后,您将使用GeneralizedNewtypeDeriving获取所有各种monad类的实例.

{-# 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
Run Code Online (Sandbox Code Playgroud)

你必须MonadTrans手工编写实例.对于堆栈中的每个变换器,lift它总是只有lift一次.

instance MonadTrans MyBranchT where
    lift = MyBranchT . lift . lift
Run Code Online (Sandbox Code Playgroud)

如果有添加新的有意义的能力堆栈的任何碎片X对自己也想定义一个新MonadX的这些功能类,编写MonadX实例为每个单子transfomers的(StateT,ExceptT,ContT等),如果可能的话,并从中获得MonadX实例你的变压器堆栈(MyBranchT).

你也通常会做一个类型同义词MyBranchT Identity和功能runMyBranchTrunMyBranch

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
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢,这是一个很好的答案。我可以不使用诸如“GeneralizedNewtypeDeriving”之类的语言扩展吗? (2认同)
  • @JakubDaniel 当然,您可以手动编写所有实例。但是你不想(并且可能会搞砸),因此`GeneralizedNewtypeDeriving`。 (2认同)
  • 要查看派生的实际代码,请在 GHCi 中运行命令 `:set -ddump-deriv`,它会向您显示 GHC 慷慨地为您编写的所有代码。`数据AB = A | B deriving Eq` dumps `instance GHC.Classes.Eq Ghci5.AB where (GHC.Classes.==) Ghci5.A Ghci5.A = GHC.Types.True; (GHC.Classes.==) Ghci5.B Ghci5.B = GHC.Types.True; (GHC.Classes.==) _ _ = GHC.Types.False` (2认同)