Haskell Language
州都
サーチ…
前書き
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)