Que*_*Liu 4 monads haskell state-monad
在Haskell中,州是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
Run Code Online (Sandbox Code Playgroud)
非常感谢!
它真的归结为理解状态是同构的s -> (a, s)
.因此,在monadic动作中"包装"的任何值都是将转换应用于某个状态s
(有状态计算生成a
)的结果.
在两个有状态计算之间传递状态
f :: a -> State s b
g :: b -> State s c
Run Code Online (Sandbox Code Playgroud)
对应于用它们组成 >=>
f >=> g
Run Code Online (Sandbox Code Playgroud)
或使用 >>=
\a -> f a >>= g
Run Code Online (Sandbox Code Playgroud)
这里的结果是
a -> State s c
Run Code Online (Sandbox Code Playgroud)
它是一种有状态的动作,s
以某种方式转换某些基础状态,允许访问某些状态a
并产生一些状态c
.因此允许整个转换依赖于,a
并且c
允许值取决于某个状态s
.这正是您希望表达有状态计算的内容.整洁的东西(以及将这种机器表达为monad的唯一目的)是你不必费心去传递状态.但要了解它是如何完成的,请参考>>=
关于hackage的定义),暂时忽略它是变换器而不是最终的monad).
m >>= k = StateT $ \ s -> do
~(a, s') <- runStateT m s
runStateT (k a) s'
Run Code Online (Sandbox Code Playgroud)
你可以忽略包装和使用展开StateT
和runStateT
,这里m
是形式s -> (a, s)
,k
是形式的a -> (s -> (b, s))
,和你想产生一个有状态的转变s -> (b, s)
.所以结果将是一个功能s
,生产b
你可以使用,k
但你需要a
先,你如何生产a
?您可以m
将其应用于状态s
,s'
从第一个monadic动作获得修改状态m
,并将该状态传递到(k a)
(类型为s -> (b, s)
).正是在这里,国家s
已经m
成为s'
并k
成为最终的一部分s''
.
对于你作为这种机制的用户,这仍然是隐藏的,这是monad的巧妙之处.如果你想要一个状态沿着某些计算进化,你可以从表达为State
-actions的小步骤构建计算,然后让let do
-notation或bind(>>=
)来进行链接/传递.
之间唯一的区别>>=
和>>
是,你要么护理或不关心非公有制结果.
a >> b
Run Code Online (Sandbox Code Playgroud)
实际上相当于
a >>= \_ -> b
Run Code Online (Sandbox Code Playgroud)
因此,动作输出的值是什么a
,你将它扔掉(只保留修改后的状态)并继续(通过状态)与其他动作b
.
关于你的例子
tick :: State Int Int
tick = get >>= \n ->
put (n+1) >>
return n
Run Code Online (Sandbox Code Playgroud)
你可以在do
注释中重写它
tick = do
n <- get
put (n + 1)
return n
Run Code Online (Sandbox Code Playgroud)
虽然第一种写作方式使得它可能更明确地传递了什么,第二种方式很好地展示了你如何不必关心它.
首先get
是当前状态并公开它(get :: s -> (s, s)
在一个简化的设置中),<-
表示你关心这个值并且你不想扔掉它,底层状态也在后台传递而没有改变(这就是get
工作原理) ).
然后put :: s -> (s -> ((), s))
,在删除不必要的parens后等效put :: s -> s -> ((), s)
,取一个值用(第一个参数)替换当前状态,并产生一个有状态动作,其结果是()
你丢弃的无趣值(因为你不使用<-
或因为你使用>>
而不是>>=
).由于put
基础状态已经改变,n + 1
因此它被传递.
return
对底层状态没有任何作用,它只返回它的参数.
总而言之,tick
从一些初始值开始,s
它将其更新为s+1
内部,并s
在侧面输出.
另一个例子的工作方式完全相同,>>
只是在那里用来扔掉所()
产生的put
.但状态一直在传递.