まず、フィボナッチアルゴリズムが使用されていることを確認します。考え方はタプル(0, 1)
で始まり、次は(1, 0 + 1)
、次は(1, 1 + 1)
、(2, 2 + 1)
、(3, 3 + 2)
などとなります。一般に、ステップは\(a, b) -> (b, a + b)
です。これらのタプルにフィボナッチ数があることがわかります。内部の最初のステートメントの内側に起こっている
ない、すなわち何 は(a、b)は<はにつながる。-GETのでしょうか?
ハスケルにはステートメントがなく、式だけがあります。
y <- x
は完全な表現ではありません。これはx >>= \y ->
に似ています。
y <- x
m
完全表現はx >>= \y -> m
と等価です。 y <- n
の形式ではないn
の行は、_ <- n
と等価です(let
行を除き、多分私は忘れています)。
これを使用して、私たちはdesugar do-notationを使用できます。
fib n =
flip evalState (0, 1)
(forM
[0..(n-1)]
(\_ -> get >>= (\(a, b) -> put (b, a + b)))
>>= (\_ -> get >>= (\(a, b) -> return a)))
)
は今、それはちょうどように理解>>=
、return
、get
、put
、およびについてです。
実際には、状態はタイプs -> (s, a)
の単なる機能です。それらは初期状態をとり、次の状態に他の値を加えたものを生成する。
m >>= n
a.k.a "bind"はタイプMonad m => m a -> (a -> m b) -> m b
です。私たちのモナドがState s
であればその後、これは同じです:
m
によって返さa
はn
に渡す必要があります。他に何があると思いますか?州も同様に進むと予想されますので、m
で返された州はn
にも渡されなければなりません。関数m >>= n
は、n
が返す状態と値を返す必要があります。私たちは、その後、バインドを実装する方法を知っている:そしてreturn :: a -> s -> (s, a)
と同等です
m >>= n = uncurry (flip n) . m
return :: Monad m => a -> m a
:
return = flip (,)
get :: State s s
はget :: s -> (s, s)
と同等です:
get = join (,)
put :: s -> State s()
かput :: s -> s -> (s,())
:
put s _ = (s,())
evalState :: s -> State s a -> a
またはevalState :: s -> (s -> (s, a)) -> a
:
evalState s f = snd (f s)
あなたはすべての定義を拡大し、一例で起こっている正確に何を見ることができます。しかし、直感だけで十分です。
forM
[0..(n-1)]
(\_ -> get >>= (\(a, b) -> put (b, a + b)))
我々は最初の引数がドロップされるのでn - 1
に番号0
を持つ気にしないでください。 get
が現在の状態を取得すると、put
は新しい状態を書き込みます。我々はこれを行うn
回。
累積値(単位)は気にしないので、最初のパラメータは削除されます。次に、現在の状態を取得し、ペアの最初の要素だけを投影します。これが私たちが探している最終的な答えです。
flip evalState (0, 1) …
最後に、最初の状態(0, 1)
から実行します。
この実装にはいくつかのクリーンアップがあります。まず、我々は範囲[0..(n-1)]
に気にしない、我々はちょうどアクションn
回を繰り返す気にする。これを行うために、より直接的な方法は以下の通りです:
replicateM n (get >>= \(a, b) -> put (b, a + b))
結果は未使用である単位のリストであるので、より効率的なバージョンは次のとおりです。
replicateM_ n (get >>= \(a, b) -> put (b, a + b))
のための機能がすでにありますget
の後にput
のmodify
と命名された共通パターン、これは\f -> get >>= put . f
と定義される。したがって:
replicateM_ n (modify (\(a, b) -> (b, a + b)))
が続いて部分がある:
>>= (\_ -> get >>= (\(a, b) -> return a)))
我々は>>
を使用することができ、以前の結果を気にしないでくださいいつでも。
>> get >>= (\(a, b) -> return a))
これは、次のとおりです。
>> get >>= return . fst
m >>= return . f
が簡素化fmap f m
へ:今
>> fmap fst get
我々持ち、合計:
fib n =
evalState
( replicateM_ n (modify (\(a, b) -> (b, a + b)))
>> fmap fst get
)
(0, 1)
我々はまた、比較のために、使用する場合があります:
私は愚かだから、その後
fib n =
fst
(evalState
( replicateM_ n (modify (\(a, b) -> (b, a + b)))
>> get
)
(0, 1)
)
そして:
fib =
fst
. flip evalState (0, 1)
. (>> get)
. flip replicateM_ (modify (snd &&& uncurry (+)))
あなたはこっち状態モナドを使用したいと思うのはなぜ?
あなたはそうではありません。状態値のみを使用するため、これは明らかです。他の値は常に単位であり、破棄されます。換言すれば、冒頭にn
(すなわち、どのフィボナッチ数を見つけるか)が必要なだけで、累積タプルのみが必要となる。
時々、h . g . f
のような文字列があると思うかもしれませんが、ただ1つではなく2つの引数を送信したいと思うことがあります。つまり、State
が適用可能な場合があります。
一部の関数を読み込んでいくつかの状態(2番目の引数)を書き込むか、またはその両方を行う場合、State
が請求書に適合します。読者だけがいる場合はReader
を使用し、ライターのみがある場合はWriter
を使用してください。
この例を変更して、State Monadをより使いやすくすることができます。私はタプルを消滅させるでしょう!だから、
fib =
flip evalState 0
. foldr (=<<) (return 1)
. flip replicate (\x -> get >>= \y -> put x $> x + y)
2)までは、実際のコードではおもしろい例ではありません。 –