2015-09-14 88 views
8

我試圖採用ExceptT a (StateT A M),對於某些具體類型A和monad M,並將它們包裹到我的新自定義monads中。整理Monads - 將monad變壓器的應用轉換爲新型monad

首先我確定了StateT A M在其他環境中經常出現,因此我決定這將是最好的單獨包裝在一個單子M1,然後包裝成ExceptT a M1M2

期望的特性是使MonadStateM1M2實例和類M(讓我們假設它被稱爲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 MExceptT ...類型的連接。

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

現在已經實施的變壓器消失了,我想我正在嘗試做一些沒有多大意義的事情。所以我的問題是:如何正確地將這種行爲包裝到新的複合單體中,從而能夠訪問下面的圖層,從而避免不必要的提升,保持事物清晰和組織良好。

回答

9

正常模式是爲您的整個變壓器堆棧定義一種新類型。

data A = A 
data E = E 

newtype MyBranchT m a = MyBranchT { getMyBranchT :: ExceptT E (StateT A m) a } 

如果堆棧中有任何部分自行添加有意義的新功能,您還需要爲這些部分定義新類型。

然後您使用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 

您必須手動編寫MonadTrans實例。 lift對於堆棧中的每個變壓器總是隻有lift

instance MonadTrans MyBranchT where 
    lift = MyBranchT . lift . lift 

如果有對自己有意義的添加新功能X你也想定義一個新MonadX類的能力,寫MonadX情況下,每個單子transfomers(StateT堆疊的任何碎片,ExceptTContT等),並且爲您的變壓器堆棧(MyBranchT)派生MonadX實例。

你也通常會做出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 
+0

非常感謝你,這是一個偉大的答案。我可以以某種方式沒有像'GeneralizedNewtypeDeriving'這樣的語言擴展嗎? – jakubdaniel

+0

@JakubDaniel當然,你可以手動編寫所有的實例。但是你不想(也可能會搞亂一個),因此是'GeneralizedNewtypeDeriving'。 – Cirdec

+0

要查看得到的實際代碼,請在GHCi中運行命令':set -ddump-deriv',它會向您顯示GHC慷慨地爲您編寫的所有代碼。 'data AB = A | B推導出Eq'dumps'實例GHC.Classes.Eq Ghci5.AB其中(GHC.Classes。==)Ghci5.A Ghci5.A = GHC.Types.True; (GHC.Classes。==)Ghci5.B Ghci5.B = GHC.Types.True; (GHC.Classes。==)_ _ = GHC.Types.False' –