2015-09-14 15 views
8

ExceptT a (StateT A M)、具体的なタイプはA、モナドMとし、それらを新しいカスタムモナドにまとめます。モナドをきれいにする - モナド変圧器を新しいタイプのモナドに応用すること

まず私はStateT A Mその他のコンテキストで頻繁に表示されますので、私はそれだけではモナドM1で、その後M2ExceptT a M1をラップすることをラップするのがベストだろうことを決めたことを確認しました。

所望の特性は、(それがMyMonadClass呼ばれると仮定することができます)M1M2MonadStateのインスタンスとMのクラスを作ることです。 M2MonadErrorのインスタンスにする必要があります。

まず私は、単純な型シノニムによって開始:

type MyState = StateT A M 
type MyBranch a = ExceptT a MyState 

その後、私は私が最初のインスタンス(インスタンスを実行せずに)宣言し、私が最初に捕まってしまったのthatsをスケッチだろうと思いました。 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) } 

すでに実装されているトランスが消えてしまったので、あまり意味のないことをしようとしていると思います。だから私の質問です:どのように正しく1つは、不必要な持ち上げを避けるために物事を明確かつ組織されたままにすることができます下層にアクセス可能にする新しい複合モナドにこのような行動をラップするでしょう。

答えて

9

通常のパターンは、完全なトランススタックに新しいタイプを定義することです。

data A = A 
data E = E 

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

意味のある新しい機能を追加するスタックがあれば、これらの部分に新しいタイプを定義することもできます。

次に、GeneralizedNewtypeDerivingを使用して、さまざまなモナドクラスのすべてのインスタンスを取得します。

{-# 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は、スタック内の変圧器ごとに常に1度だけliftです。

instance MonadTrans MyBranchT where 
    lift = MyBranchT . lift . lift 

あなたはまた、それらの機能のための新しいMonadXクラスを定義モナドtransfomers(StateTごとにMonadXインスタンスを記述します。自分でXを意味のある新機能を追加し、スタックのいずれかの部分がある場合は、ExceptTContTなど)を使用して、トランススタック(MyBranchT)のMonadXインスタンスを取得します。

また、通常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

MyBranchT Identityや関数の型シノニムを作ってあげる、これは素晴らしい答えです、ありがとうございました。 'GeneralizedNewtypeDeriving'のような言語拡張機能を使わなくても何とかできますか? – jakubdaniel

+0

@JakubDanielもちろん、すべてのインスタンスを手作業で書くことができます。しかし、あなたはしたくない(そしておそらくそれを混乱させるでしょう)、したがって 'GeneralizedNewtypeDeriving'。 – Cirdec

+0

GHCiで ':set -ddump-deriv'コマンドを実行した実際のコードを見るには、GHCが寛容に書いたコードをすべて表示します。 'data AB = A | Bを導出するEq'ダンプ 'インスタンス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' –

関連する問題