サーチ…


前書き

状態のモナドは、モナドの各計算実行中に変化する可能性のある状態を担う一種のモナドです。実装は、通常のフォームのあるState sa搬送し、潜在型の状態変更計算表すs 、タイプの結果生成さa 、しかし、用語「状態モナド」は、一般的状態を運ぶ任意のモナドを指すことができます。 mtlパッケージとtransformersパッケージは、状態モナドの一般的な実装を提供します。

備考

Haskellの初心者は、多くの場合、離れてから敬遠Stateモナドとタブーのような関数型プログラミングの主張利益は状態の回避であるので、あなたは、あなたが使用している場合ことを失わないように扱うState ?より微妙な見方は次のとおりです。

  • 状態は、 少量の制御された用量で有用であり得る;
  • Stateタイプは、線量を非常に正確に制御する能力を提供する。

あなたがaction :: State saを持っているなら、これはあなたにそれを伝えます:

  • actionは状態に依存するため特殊です。
  • 状態は型を持つs 、そのactionあなたのプログラムのみで任意の古い値によって影響を受けることができないsか、いくつかから到達可能ないくつかの値s
  • runState :: State sa -> s -> (a, s)はステートフルアクションの周りに "障壁"を置くので、そのバリデーションの外側そのバリデーションを見ることができません

これは、特定のシナリオでStateを使用するかどうかを判断するための優れた基準です。あなたは、あなたのコードが状態のスコープを最小限に抑えていることを見たいと思っています。狭い型をs選択し、 runStateをできるだけ「底」に近づけることによって(あなたのアクションが可能。

カウンタを使用してツリーのノードに番号を付ける

次のようなツリーデータ型があります。

data Tree a = Tree a [Tree a] deriving Show

また、ツリーの各ノードにインクリメントカウンタから数値を割り当てる関数を記述したいとします。

tag :: Tree a -> Tree (a, Int)

遠い道のり

最初に、 Stateモナドの低レベルのメカニックスがきれいに説明されているので、私たちは遠くにやっていきます。

import Control.Monad.State

-- Function that numbers the nodes of a `Tree`.
tag :: Tree a -> Tree (a, Int)
tag tree = 
    -- tagStep is where the action happens.  This just gets the ball
    -- rolling, with `0` as the initial counter value.
    evalState (tagStep tree) 0

-- This is one monadic "step" of the calculation.  It assumes that
-- it has access to the current counter value implicitly.
tagStep :: Tree a -> State Int (Tree (a, Int))
tagStep (Tree a subtrees) = do
    -- The `get :: State s s` action accesses the implicit state
    -- parameter of the State monad.  Here we bind that value to
    -- the variable `counter`.
    counter <- get 

    -- The `put :: s -> State s ()` sets the implicit state parameter
    -- of the `State` monad.  The next `get` that we execute will see
    -- the value of `counter + 1` (assuming no other puts in between).
    put (counter + 1)

    -- Recurse into the subtrees.  `mapM` is a utility function
    -- for executing a monadic actions (like `tagStep`) on a list of
    -- elements, and producing the list of results.  Each execution of 
    -- `tagStep` will be executed with the counter value that resulted
    -- from the previous list element's execution.
    subtrees' <- mapM tagStep subtrees  

    return $ Tree (a, counter) subtrees'

リファクタリング

counterをpostIncrementアクションに分割します。

我々はビットget 、その後ティンに現在のカウンタをしてはputティンカウンタを+ 1に分裂することができるpostIncrement多くのCスタイルの言語が提供するものと同様の作用、:

postIncrement :: Enum s => State s s
postIncrement = do
    result <- get
    modify succ
    return result

ツリーウォークを高階関数に分割する

ツリーウォークロジックは、次のように独自の関数に分割できます。

mapTreeM :: Monad m => (a -> m b) -> Tree a -> m (Tree b)
mapTreeM action (Tree a subtrees) = do
    a' <- action a
    subtrees' <- mapM (mapTreeM action) subtrees
    return $ Tree a' subtrees'

この関数とpostIncrement関数をpostIncrementて、 postIncrementを書き直すことができtagStep

tagStep :: Tree a -> State Int (Tree (a, Int))
tagStep = mapTreeM step
    where step :: a -> State Int (a, Int)
          step a = do 
              counter <- postIncrement
              return (a, counter)

Traversableクラスを使用する

上記のmapTreeMソリューションはTraversableクラスのインスタンスに簡単に書き換えることができます。

instance Traversable Tree where
    traverse action (Tree a subtrees) = 
        Tree <$> action a <*> traverse action subtrees

これは、 Monad代わりにApplicative<*>演算子)を使用する必要があることに注意してください。

これで、プロのようなtagを書くことができます:

tag :: Traversable t => t a -> t (a, Int)
tag init t = evalState (traverse step t) 0
    where step a = do tag <- postIncrement
                      return (a, tag)

これはTree型だけでなくTraversable型でも機能します。

Traversable定型文を削除する

GHCには、上記のインスタンスを書く必要がないDeriveTraversable拡張機能があります:

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}

data Tree a = Tree a [Tree a]
            deriving (Show, Functor, Foldable, Traversable)


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