从使用Haskell进行功能性思考(第248页):
您可以将
State s a
类型视为type State s a = s -> (a,s)
...
put :: s -> State s () get :: State s s state :: (s -> (a,s)) -> State s a
...
state
可以使用put
和get
来定义:state f = do {s <- get; let (a,s') = f s; put s'; return a}
我认为可以这样重写:
get >>= \s ->
let (a,s') = fs in
put s' >> return a
那么put s'
的目的是什么,如果>>
抛弃其返回值?
答案 0 :(得分:3)
>>
不会丢弃第一个参数中的所有内容。状态monad的定义(忽略newtype
)
a >> b = \s -> let (x, s') = a s in b s'
因此>>
使用了第一个参数的状态,但忽略了'返回值'(结果的x
组件)。这是状态monad的重点 - 跟踪状态的变化,而程序员不必明确地将它们考虑在内。
显然,OP没有正确解释这个问题,所以这里是你如何得出上面的定义。所有monad中>>
的定义是
a >> b = a >>= \ _ -> b
状态monad的>>=
定义(忽略newtype
s)是
a >>= f = \ s -> let (x, s') = a s in f x s'
现在,将>>=
的定义替换为上面>>
的定义并简化,我们得到:
a >> b = let f = \ _ -> b in \ s -> let (x, s') = a s in f x s'
= \ s -> let (x, s') = a s in (\ _ -> b) x s'
= \ s -> let (x, s') = a s in b s'
答案 1 :(得分:3)
那么
put s'
的目的是什么,如果>>
抛弃其返回值?
(>>)
会丢弃返回值,但我们不会使用put
作为返回值。 put
的类型为:
put :: s -> State s ()
返回值为put
是()
,而()
大部分只是一个无趣的占位符。 put
所做的有意义的部分 - 替换状态 - 没有反映在返回值中。类似的情况是putStrLn :: String -> IO ()
的类型。
答案 2 :(得分:3)
fib :: Int -> Int
fib 1 = 0
fib 2 = 1
fib n = fib (n-1) + fib (n-2)
是一个使用monad抽象来封装效果的示例。在这种情况下,操作的效果很重要,但它可能没有有意义的返回值,这是完全正常的。
我将举例说明。考虑每个人最喜欢的递归算法,Fibonacci序列:
fib
我们都知道这是计算这些数字的一种非常低效的方法,但 效率如何呢?如果我们使用较小的语言,我们可能会试图破解一个可变变量并在每次调用State
时递增它。我们可以使用fib' :: Int -> State Int Int
fib' 1 = modify (+1) >> return 0
fib' 2 = modify (+1) >> return 1
fib' n = modify (+1) >> (+) <$> fib' (n-1) <*> fib' (n-2)
以纯函数方式在Haskell中执行类似的操作。
让我们定义一个新版本的fib:
> runState (fib' 7) 0
(8,25)
> runState (fib' 10) 0
(34,109)
> runState (fib' 30) 0 -- this takes about 5 seconds on my machine
(514229,1664079)
现在我们可以同时计算第n个数字并计算调用次数:
modify (+1)
好的,这很好但是它如何回答这个问题?上述实现中要注意的要点是>>
。这具有向计数器添加1的效果,但是它本身没有任何有用的结果。我们使用{{1}}对下一个操作进行排序,它有一个有用的结果,即计算。