State モナドは、最初はややこしく見えます。Norman Ramsey が提案したように、最初から実装する方法を説明しましょう。警告、これはかなり長いです!
まず、State には 2 つの型パラメーターがあります。含まれている状態データの型と、計算の最終結果の型です。ここでは、それぞれの型変数としてstateData
とを使用します。result
考えてみれば、これは理にかなっています。状態ベースの計算の特徴は、出力を生成しながら状態を変更することです。
次のように、型コンストラクターが関数を状態から変更された状態と結果に変換することはあまり明白ではありません。
newtype State stateData result = State (stateData -> (result, stateData))
したがって、モナドは「状態」と呼ばれますが、モナドによってラップされる実際の値は、含まれる状態の実際の値ではなく、状態ベースの計算の値です。
それを念頭に置いてrunState
、 State モナドで計算を実行するために使用される関数が、実際にはラップされた関数自体のアクセサにすぎず、次のように定義できることに驚かないでください。
runState (State f) = f
では、State 値を返す関数を定義するとはどういう意味でしょうか? State がモナドであるという事実をしばらく無視して、基礎となる型だけを見てみましょう。まず、この関数を考えてみましょう (これは実際には状態に対して何もしません):
len2State :: String -> State Int Bool
len2State s = return ((length s) == 2)
State の定義を見ると、ここではstateData
型がInt
であり、result
型が であることがわかります。そのBool
ため、データ コンストラクターによってラップされた関数の型は でなければなりませんInt -> (Bool, Int)
。ここで、State-less バージョンのlen2State
-- 明らかに、 type を持っていると想像してくださいString -> Bool
。では、そのような関数を State ラッパーに適合する値を返す関数に変換するにはどうすればよいでしょうか?
明らかに、変換された関数はInt
状態値を表す 2 番目のパラメーターを取る必要があります。また、別の状態値を返す必要がありますInt
。この関数では状態に対して実際には何もしていないので、明らかな処理を行いましょう。その int をそのまま渡します。以下は、State-less バージョンに関して定義された State-shape 関数です。
len2 :: String -> Bool
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s i = (len2' s, i)
しかし、それはちょっとばかげていて冗長です。変換を一般化して、結果の値を渡し、何でも State のような関数に変換できるようにしましょう。
convert :: Bool -> (Int -> (Bool, Int))
convert r d = (r, d)
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s = convert (len2 s)
状態を変更する関数が必要な場合はどうすればよいでしょうか? convert
状態を渡すように書いたので、明らかに で構築することはできません。シンプルにして、状態を新しい値で上書きする関数を書きましょう。どのようなタイプが必要ですか?新しい状態の値には が必要でInt
、もちろん関数を返す必要がありstateData -> (result, stateData)
ます。それが State ラッパーに必要だからです。result
状態値を上書きしても、実際には State 計算の外部に意味のある値はありません。したがって、ここでの結果()
は、Haskell で「値なし」を表すゼロ要素のタプルになります。
overwriteState :: Int -> (Int -> ((), Int))
overwriteState newState _ = ((), newState)
それは簡単でした!では、実際にその状態データを使って何かをしてみましょう。len2State
上記をより賢明なものに書き直してみましょう: 文字列の長さと現在の状態値を比較します。
lenState :: String -> (Int -> (Bool, Int))
lenState s i = ((length s) == i, i)
前に行ったように、これをコンバーターとステートレス関数に一般化できますか? それほど簡単ではありません。len
関数は状態を引数として取る必要がありますが、状態を「認識」させたくありません。ぎこちない、確かに。ただし、すべてを処理する簡単なヘルパー関数を作成できます。状態値を使用する必要がある関数を指定すると、値が渡され、状態型の関数にすべてがパッケージ化されます。len
賢明なことは何も残していません。
useState :: (Int -> Bool) -> Int -> (Bool, Int)
useState f d = (f d, d)
len :: String -> Int -> Bool
len s i = (length s) == i
lenState :: String -> (Int -> (Bool, Int))
lenState s = useState (len s)
ここで、注意が必要なのは、これらの関数をつなぎ合わせたい場合はどうするかということです。文字列で使用したいとしましょうlenState
。結果が false の場合は状態値を 2 倍にし、文字列をもう一度チェックして、いずれかのチェックが成功した場合は最後に true を返します。このタスクに必要なパーツはすべて揃っていますが、すべてを書き出すのは大変です。それぞれが状態のような関数を返す 2 つの関数を自動的に連鎖させる関数を作成できますか? 確実なこと!result
最初の関数によって返される State 関数と、前の関数の型を引数として取る関数の 2 つを引数として取るようにする必要があるだけです。それがどのようになるか見てみましょう:
chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int))
chainStates prev f d = let (r, d') = prev d
in f r d'
これが行っているのは、最初の状態関数を何らかの状態データに適用し、次に 2 番目の関数を結果と変更された状態データに適用することだけです。シンプルですね。
さて、興味深い部分: と の間chainStates
でconvert
、ステートレス関数の任意の組み合わせをステート対応関数にほぼ変換できるはずです! 今必要なのはuseState
、結果として状態データを返す の置き換えだけです。これにより、chainStates は、プルしているトリックについて何も知らない関数にそれを渡すことができます。また、ラムダを使用して前の関数からの結果を受け取り、それらに一時的な名前を付けます。さて、これを実現しましょう:
extractState :: Int -> (Int, Int)
extractState d = (d, d)
chained :: String -> (Int -> (Bool, Int))
chained str = chainStates extractState $ \state1 ->
let check1 = (len str state1) in
chainStates (overwriteState (
if check1
then state1
else state1 * 2)) $ \ _ ->
chainStates extractState $ \state2 ->
let check2 = (len str state2) in
convert (check1 || check2)
そして試してみてください:
> chained "abcd" 2
(True, 4)
> chained "abcd" 3
(False, 6)
> chained "abcd" 4
(True, 4)
> chained "abcdef" 5
(False, 10)
もちろん、State は実際には State のような関数をラップしてそれらから遠ざけるモナドであることを忘れてはなりません。それとも彼らはしますか?衝撃的なひねりを加えて、実際の State モナドはすべて同じ関数を異なる名前で提供することが判明しました。
runState (State s) = s
return r = State (convert r)
(>>=) s f = State (\d -> let (r, d') = (runState s) d in
runState (f r) d')
get = State extractState
put d = State (overwriteState d)
>>= は chainStates とほぼ同じですが、chainStates を使用して定義する良い方法がありませんでした。まとめとして、実際のStateを使用して最後の例を書き直すことができます。
chained str = get >>= \state1 ->
let check1 = (len str state1) in
put (if check1
then state1 else state1 * 2) >>= \ _ ->
get >>= \state2 ->
let check2 = (len str state2) in
return (check1 || check2)
または、同等の do 表記ですべて詰め込みます。
chained str = do
state1 <- get
let check1 = len str state1
_ <- put (if check1 then state1 else state1 * 2)
state2 <- get
let check2 = (len str state2)
return (check1 || check2)