[英]Understanding Monadic Fibonacci
我正在學習haskell和學習單子。 我已經看過並閱讀了各種教程,並為狀態monad編寫了一些簡單的示例,但是我無法理解以下代碼(摘自Haskell Wiki):
import Control.Monad.State
fib n = flip evalState (0,1) $ do
forM [0..(n-1)] $ \_ -> do
(a,b) <- get
put (b,a+b)
(a,b) <- get
return a
我的問題歸結為以下幾點:
(a,b)<-get
結果是什么。 對於某些具體示例, a
和b
的值是多少。 在此示例中,狀態是一對,其中包含在序列中生成的前兩個數字。 最初(0, 1)
提供給evalState
。
get
的類型是MonadState sm => ms
因此在內部do
塊中
(a, b) <- get
獲取狀態對並將a
和b
分別綁定到第一和第二個元素。 然后在以下put
更新狀態。
因此,狀態為:
(0, 1), (1, 1), (1, 2), (3, 2), (3, 5), ...
外層
(a, b) <- get
return a
解壓縮最終狀態值並返回第一個元素。
首先讓我們弄清楚正在使用的斐波那契算法。 這個想法是從元組(0, 1)
,然后找到下一個(1, 0 + 1)
,下一個是(1, 1 + 1)
, (2, 2 + 1)
, (3, 3 + 2)
,依此類推。 通常,該步驟為\\(a, b) -> (b, a + b)
。 您可以看到在這些元組中是斐波那契數。
內部do的第一個語句的內容是什么,即(a,b)<-得到的結果是什么?
Haskell沒有語句,只有表達式。
y <- x
不是完整的表達式。 它類似於x >>= \\y ->
。
y <- x
m
是一個完整的表達式,等效於x >>= \\y -> m
。 不以y <- n
形式出現的行n
等效於_ <- n
(不包括let
行以及可能我忘記的其他行)。
使用此,我們可以對糖做注釋符號。
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)
類型s -> (s, a)
函數。 它們采用初始狀態,並產生下一個狀態加上一些其他值。
m >>= n
aka“ bind”的類型為Monad m => ma -> (a -> mb) -> mb
。 然后,如果我們的Monad是State s
,則與:
m >>= n ::
( s -> (s, a))
-> (a -> s -> (s, b))
-> ( s -> (s, b))
m
返回的a
必須傳遞給n
。 我們還能猜到什么? 我們希望狀態也會傳遞,因此m
返回的狀態也必須傳遞給n
。 函數m >>= n
必須返回狀態和值n
回報。 然后,我們知道如何實現綁定:
m >>= n = uncurry (flip n) . m
return :: Monad m => a -> ma
然后等於return :: Monad m => a -> ma
return :: a -> s -> (s, a)
:
return = flip (,)
get :: State ss
等於get :: s -> (s, s)
:
get = join (,)
put :: s -> State s ()
或put :: s -> s -> (s, ())
:
put s _ = (s, ())
evalState :: s -> State sa -> a
evalState :: s -> (s -> (s, a)) -> a
evalState :: s -> State sa -> 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
的功能,其后跟名為modify
的put
,其定義為\\f -> get >>= put . f
\\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
m >>= return . f
簡化為fmap fm
:
>> 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 (+)))
您為什么要在這里使用州立monad?
你不會的 這很清楚,因為我們只使用狀態值; 另一個值始終是單位並被丟棄。 換句話說,我們一開始只需要n
(即找到哪個斐波那契數),之后我們只需要累積的元組即可。
有時您認為具有類似h . g . f
的字符串h . g . f
h . g . f
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)
因此,文檔狀態為: get :: ms -- Return the state from the internals of the monad
(請參見此處 )。
但是我非常記得,當我試圖將自己的頭纏在莫納德州時,這並沒有太大幫助。
我只能建議在ghci中使用:i
和:t
並測試不同的子表達式。 只是為了感受一下。 有點像這樣:
import Control.Monad.State.Lazy
runState (get) 0
runState (get >>= \x -> put (x+1)) 0
:t return 1 :: State Int Int
runState (return 1) 0
runState (return 1 >>= \x -> (get >>= \y -> return (x+y))) 0
-- Keeping a pair of (predecessor/current) in the state:
let f = (get >>= (\(a,b) -> put (b,a+b))) :: State (Int, Int) ()
runState (f >> f >> f >> f >> f >> f) (0,1)
-- only keeping the predecessor in the state:
let f x = (get >>= (\y -> put x >> return (x+y))) :: State Int Int
runState (return 1 >>= f >>= f >>= f >>= f >>= f >>= f) 0
還玩弄modify
, runState
, evalState
, execState
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.