在状态 monad 中使用 put with >>
Use of put with >> in the state monad
来自 Haskell 的功能思考(第 248 页):
You can think of type State s a
as being
type State s a = s -> (a,s)
...
put :: s -> State s ()
get :: State s s
state :: (s -> (a,s)) -> State s a
... state
can be defined using put
and 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'
的目的是什么,如果 >>
丢弃了它的 return 价值?
>>
并没有丢弃第一个参数的所有内容。状态 monad 的定义(忽略 newtype
)
a >> b = \s -> let (x, s') = a s in b s'
因此 >>
使用第一个参数的状态形式,但 'return value'(结果的 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'
So what is the purpose of put s'
, if >>
throws away its return value?
(>>)
确实丢弃了 return 值,但我们不使用 put
作为 return 值。 put
的类型是:
put :: s -> State s ()
return 的值是 put
的一个 ()
,而 ()
在大多数情况下只是一个无趣的占位符。 put
所做的有意义的部分——替换状态——没有反映在 return 值中。类似的情况是 putStrLn :: String -> IO ()
.
类型
State
是使用 monad 抽象来封装 effect 的示例。在这种情况下,具有操作效果很重要的操作是完全正常的,但它可能没有有意义的 return 值。
我会举例说明。考虑每个人最喜欢的递归算法,斐波那契数列:
fib :: Int -> Int
fib 1 = 0
fib 2 = 1
fib n = fib (n-1) + fib (n-2)
我们都知道这是一种非常低效的计算这些数字的方法,但是如何低效呢?如果我们使用的是一种更小的语言,我们可能会试图破解一个可变变量并在每次调用 fib
时递增它。我们可以使用 State
.
以纯函数的方式在 Haskell 中做类似的事情
让我们定义一个新版本的 fib:
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)
现在我们可以同时计算第 n 个数字并计算调用了多少次:
> 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)
好的,这很好,但它如何回答问题?上面实现中需要注意的一点是modify (+1)
。这具有将计数器加 1 的效果,但它本身没有任何有用的结果。我们使用 >>
将其与下一个操作排序,这确实有一个有用的结果,即计算。
来自 Haskell 的功能思考(第 248 页):
You can think of type
State s a
as beingtype State s a = s -> (a,s)
...
put :: s -> State s () get :: State s s state :: (s -> (a,s)) -> State s a
...
state
can be defined usingput
andget
: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'
的目的是什么,如果 >>
丢弃了它的 return 价值?
>>
并没有丢弃第一个参数的所有内容。状态 monad 的定义(忽略 newtype
)
a >> b = \s -> let (x, s') = a s in b s'
因此 >>
使用第一个参数的状态形式,但 'return value'(结果的 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'
So what is the purpose of
put s'
, if>>
throws away its return value?
(>>)
确实丢弃了 return 值,但我们不使用 put
作为 return 值。 put
的类型是:
put :: s -> State s ()
return 的值是 put
的一个 ()
,而 ()
在大多数情况下只是一个无趣的占位符。 put
所做的有意义的部分——替换状态——没有反映在 return 值中。类似的情况是 putStrLn :: String -> IO ()
.
State
是使用 monad 抽象来封装 effect 的示例。在这种情况下,具有操作效果很重要的操作是完全正常的,但它可能没有有意义的 return 值。
我会举例说明。考虑每个人最喜欢的递归算法,斐波那契数列:
fib :: Int -> Int
fib 1 = 0
fib 2 = 1
fib n = fib (n-1) + fib (n-2)
我们都知道这是一种非常低效的计算这些数字的方法,但是如何低效呢?如果我们使用的是一种更小的语言,我们可能会试图破解一个可变变量并在每次调用 fib
时递增它。我们可以使用 State
.
让我们定义一个新版本的 fib:
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)
现在我们可以同时计算第 n 个数字并计算调用了多少次:
> 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)
好的,这很好,但它如何回答问题?上面实现中需要注意的一点是modify (+1)
。这具有将计数器加 1 的效果,但它本身没有任何有用的结果。我们使用 >>
将其与下一个操作排序,这确实有一个有用的结果,即计算。