首先讓我們明確正在使用的斐波那契算法。這個想法是從元組(0, 1)
開始,然後找到下一個爲(1, 0 + 1)
,下一個爲(1, 1 + 1)
,(2, 2 + 1)
,(3, 3 + 2)
,依此類推。通常,步驟是\(a, b) -> (b, a + b)
。你可以看到在這些元組中是斐波那契數。
什麼是內做的第一個語句,即沒有(A,B)< -get導致成什麼 裏面去?
Haskell沒有語句,只有表達式。
y <- x
不是一個完整的表達式。它類似於x >>= \y ->
。
y <- x
m
是一個完整的表達,相當於x >>= \y -> m
。一行n
非y <- 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
又名「綁定」的類型爲Monad m => m a -> (a -> m b) -> m b
。然後,如果我們的單子是State s
,這是一樣的:
的由m
返回a
將被傳遞到n
。我們還能猜到什麼?我們預計國家也會傳遞,所以m
返回的狀態也必須傳遞給n
。函數m >>= n
必須返回n
返回的狀態和值。然後,我們知道如何實現綁定:
m >>= n = uncurry (flip n) . m
return :: Monad m => a -> m a
然後將其等同於return :: a -> s -> (s, 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)))
我們不關心具有數字0
到n - 1
所以第一個參數被丟棄。 get
檢索當前狀態,然後put
寫入新狀態。我們這樣做n
次。
>>= (\_ -> get >>= (\(a, b) -> return a)))
我們不關心累計值(這是單位),所以第一個參數被丟棄。然後我們得到當前狀態並且只是項目的第一個元素。這是我們正在尋找的最終答案。
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
,但您希望發送兩個參數而不是一個參數。那是State
可能適用。
如果某些函數讀取並且某些函數寫入狀態(第二個參數),或者兩者都執行,那麼State
就符合要求。如果只有讀者,則使用Reader
,如果只有作者,則使用Writer
。
我們可以改變這個例子來更好地使用State Monad。我會讓這個元組消失!
fib =
flip evalState 0
. foldr (=<<) (return 1)
. flip replicate (\x -> get >>= \y -> put x $> x + y)
至於2),你......不會在實際的代碼中,它只是一個玩具的例子。 –