在Haskell中,State是monad被传递来提取和存储状态。在以下两个示例中,两个都使用>>
传递状态monad,并且密切验证(通过函数内联和减少)确认状态确实传递到下一步。
但这似乎不太直观。那么这是否意味着当我想要传递状态monad时,我只需要>>
(或>>=
和lambda表达式\s -> a
,其中s
在a
中不可用})?任何人都可以为这一事实提供直观的解释,而无需减少功能吗?
-- the first example
tick :: State Int Int
tick = get >>= \n ->
put (n+1) >>
return n
-- the second example
type GameValue = Int
type GameState = (Bool, Int)
playGame' :: String -> State GameState GameValue
playGame' [] = get >>= \(on, score) -> return score
playGame' (x: xs) = get >>= \(on, score) ->
case x of
'a' | on -> put (on, score+1)
'b' | on -> put (on, score-1)
'c' -> put (not on, score)
_ -> put (on, score)
>> playGame xs
非常感谢!
答案 0 :(得分:5)
归结为理解状态与s -> (a, s)
是同构的。所以任何价值"包裹"在monadic动作中是将转换应用于某个状态s
(有状态计算生成a
)的结果。
在两次有状态计算之间传递状态
f :: a -> State s b
g :: b -> State s c
对应于使用>=>
f >=> g
或使用>>=
\a -> f a >>= g
这里的结果是
a -> State s c
这是一种有状态的操作,它以某种方式转换某些基础状态s
,允许访问某些a
并生成一些c
。因此,允许整个转换依赖于a
,并且允许值c
依赖于某个州s
。这正是您希望表达有状态计算的内容。整洁的东西(以及将这种机器表达为monad的唯一目的)是你不必费心去传递状态。但要了解它是如何完成的,请参考hackage上的>>=
的定义,暂时忽略它是变换器而不是最终的monad)。
m >>= k = StateT $ \ s -> do
~(a, s') <- runStateT m s
runStateT (k a) s'
您可以使用StateT
和runStateT
忽略包装和展开,此处m
的格式为s -> (a, s)
,k
格式为a -> (s -> (b, s))
你希望产生一个有状态的转换s -> (b, s)
。因此,结果将是s
的函数,要生成b
,您可以使用k
,但首先需要a
,如何生成a
?你可以m
将其应用到状态s
,从第一个monadic动作s'
获得修改后的状态m
,然后将该状态传递给{{1} (类型为(k a)
)。在这里,州s -> (b, s)
已经通过s
成为m
并被传递到s'
以成为最终的k
。
对于你作为这种机制的用户,这仍然是隐藏的,这是关于monad的巧妙之处。如果你想让一个状态沿着某些计算发展,你可以从表达为s''
的小步骤构建计算 - 动作,并让State
- 表示法或绑定(do
)来做链接/传递。
>>=
和>>=
之间的唯一区别在于您要么关心也要不关心非州的结果。
>>
实际上等同于
a >> b
所以动作a >>= \_ -> b
输出的值是什么,你扔掉它(只保留修改后的状态)并继续(通过状态)与另一个动作a
。
关于你的例子
b
你可以用tick :: State Int Int
tick = get >>= \n ->
put (n+1) >>
return n
- 符号
do
虽然第一种写作方式使得它可能更明确地传达了什么,第二种方式很好地展示了你如何不必关心它。
首先tick = do
n <- get
put (n + 1)
return n
当前状态并在简化设置中公开get
,get :: s -> (s, s)
表示您确实关心值并且您不想丢弃它,基础状态也会在后台传递而不会发生更改(这就是<-
的工作方式)。
然后get
,在将不必要的parens删除到put :: s -> (s -> ((), s))
后等效,取一个值用(第一个参数)替换当前状态,并产生一个有状态的动作,其结果是您放弃的无趣价值put :: s -> s -> ((), s)
(因为您没有使用()
或因为您使用<-
而不是>>
)。由于>>=
基础状态已更改为put
,因此会将其传递。
n + 1
对基础状态没有任何作用,只有返回其参数。
总结一下,return
以一些初始值tick
开头,它在内部将其更新为s
,并在旁边输出s+1
。
另一个示例的工作原理完全相同,s
仅用于放弃>>
生成的()
。但是状态一直都在传递。