サーチ…


モナディックカウンター

モナド・トランスを使ってリーダ、ライター、ステート・モナドを構成する方法の例。ソースコードはこのリポジトリにあります

与えられた定数で値をインクリメントするカウンタを実装したい。

まず、いくつかの型と関数を定義します。

newtype Counter = MkCounter {cValue :: Int}
  deriving (Show)

-- | 'inc c n' increments the counter by 'n' units.
inc :: Counter -> Int -> Counter
inc (MkCounter c) n = MkCounter (c + n)

カウンタを使用して次の計算を実行するとします。

  • カウンタを0に設定する
  • インクリメント定数を3に設定する
  • カウンターを3回増分する
  • インクリメント定数を5に設定する
  • カウンターを2回インクリメントする

状態モナド状態を渡すための抽象化を提供します。状態モナドを利用して、増分関数を状態変換器として定義することができます。

-- | CounterS is a monad.
type CounterS = State Counter

-- | Increment the counter by 'n' units.
incS :: Int-> CounterS ()
incS n = modify (\c -> inc c n)

これにより、より明確かつ簡潔な方法で計算を表現することができます。

-- | The computation we want to run, with the state monad.
mComputationS :: CounterS ()
mComputationS = do
  incS 3
  incS 3
  incS 3
  incS 5
  incS 5

しかし、それぞれの呼び出しでインクリメント定数を渡す必要があります。私たちはこれを避けたいと思います。

環境の追加

リーダモナドは、周囲に環境を渡す便利な方法を提供します。このモナドは、OO世界で何が依存関係注入として知られているかを実行する関数型プログラミングで使用されます。

最も簡単なバージョンでは、リーダモナドには2つのタイプが必要です。

  • 読み込まれる値のタイプ(つまり、環境、 r以下)

  • リーダーモナド(によって返される値以下)。 a

    読者ra

しかし、状態モナドも利用する必要があります。したがって、 ReaderTトランスを使用する必要があります。

newtype ReaderT r m a :: * -> (* -> *) -> * -> *

ReaderTを使用して、次のように環境と状態でカウンタを定義できます。

type CounterRS = ReaderT Int CounterS

環境からのインクリメント定数を( askを使用して)取るincR関数を定義し、 lift関数( モナド変圧器クラスに属する)を使用するCounterSモナドの増分関数を定義します。

-- | Increment the counter by the amount of units specified by the environment.
incR :: CounterRS ()
incR = ask >>= lift . incS

リーダモナドを使用して、以下のように計算を定義することができます。

-- | The computation we want to run, using reader and state monads.
mComputationRS :: CounterRS ()
mComputationRS = do
  local (const 3) $ do
    incR
    incR
    incR
    local (const 5) $ do
      incR
      incR

要件が変更されました:ログが必要です!

ここで、我々の計算にロギングを追加して、時間内にカウンタの進化を見ることができると仮定します。

この作業を行うためのモナドである作家のモナドもあります。読者のモナドと同様に、それらを合成するので、読者のモナドトランスを使用する必要があります。

newtype WriterT w m a :: * -> (* -> *) -> * -> *

ここでw (私たちは、この値を蓄積することを可能にするモノイドをなければならない)蓄積する出力のタイプを表し、 m内部モナドであり、計算の種類。 a

次に、ロギング、環境、および状態を使用して、次のようにカウンタを定義できます。

type CounterWRS = WriterT [Int] CounterRS

また、 liftを利用することで、各インクリメント後にカウンタの値を記録するインクリメント関数のバージョンを定義することができます。

incW :: CounterWRS ()
incW = lift incR >> get >>= tell . (:[]) . cValue

今度は、ロギングを含む計算を次のように書くことができます。

mComputationWRS :: CounterWRS ()
mComputationWRS = do
  local (const 3) $ do
    incW
    incW
    incW
    local (const 5) $ do
      incW
      incW

一度にすべてを行う

この例は、作業中のモナド変圧器を示すことを意図しています。しかし、すべてのアスペクト(環境、状態、ロギング)を1回のインクリメント操作で合成することで同じ効果を得ることができます。

これを行うには、型制約を利用します。

inc' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()
inc' = ask >>= modify . (flip inc) >> get >>= tell . (:[]) . cValue

ここでは、上記の制約を満たすモナドのために働くソリューションに到達します。計算関数は以下のように定義されます:

mComputation' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()

体の中で私たちはincを利用しているからです。

たとえば、 ghci REPLで次のようにこの計算を実行できます。

runState ( runReaderT ( runWriterT mComputation' ) 15 )  (MkCounter 0)


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow