我正在学习haskell和学习monad。我已经观看并阅读了各种教程并为状态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 :(得分:4)
在此示例中,状态是包含序列中生成的前两个数字的对。最初(0, 1)
提供给evalState
。
get
的类型为MonadState s m => m s
所以内部do
块
(a, b) <- get
获取状态对并将a
和b
分别绑定到第一个和第二个元素。然后在以下put
中更新状态。
(0, 1), (1, 1), (1, 2), (3, 2), (3, 5), ...
外部
(a, b) <- get
return a
解包最终的状态值并返回第一个元素。
答案 1 :(得分:3)
首先让我们清楚使用的Fibonacci算法。我们的想法是从元组(0, 1)
开始,然后将下一个作为(1, 0 + 1)
,将下一个作为(1, 1 + 1)
,(2, 2 + 1)
,(3, 3 + 2)
,依此类推。通常,步骤为\(a, b) -> (b, a + b)
。你可以看到这些元组中的斐波那契数字。
内在的第一个陈述内容是什么,即什么 (a,b)&lt; -get result into?
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
等等。
State实际上只是s -> (s, a)
类型的函数。它们处于初始状态并产生下一个状态加上一些其他值。
m >>= n
a.k.a。&#34; bind&#34;类型为Monad m => m a -> (a -> m b) -> m b
。然后,如果我们的Monad是State s
,则与以下内容相同:
m >>= n ::
( s -> (s, a))
-> (a -> s -> (s, b))
-> ( s -> (s, b))
a
返回的m
必须传递给n
。我们还能猜到什么?我们期望状态也会传递,因此m
返回的状态也必须传递给n
。函数m >>= n
必须返回n
返回的状态和值。然后我们知道如何实现bind:
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 (+)))
为什么要在这里使用州monad?
你不会。这很清楚,因为我们只使用状态值;另一个值总是单位并被丢弃。换句话说,我们只需要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 :(得分:2)
因此,文档说明了get :: m s -- Return the state from the internals of the monad
(请参阅here)。
但我记得很清楚,当我试图绕着State 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
。