关于哈斯克尔国家monad逝世的混淆

时间:2017-02-22 06:46:13

标签: haskell monads state-monad

在Haskell中,State是monad被传递来提取和存储状态。在以下两个示例中,两个都使用>>传递状态monad,并且密切验证(通过函数内联和减少)确认状态确实传递到下一步。

但这似乎不太直观。那么这是否意味着当我想要传递状态monad时,我只需要>>(或>>=和lambda表达式\s -> a,其中sa中不可用})?任何人都可以为这一事实提供直观的解释,而无需减少功能吗?

-- 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 

非常感谢!

1 个答案:

答案 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'

您可以使用StateTrunStateT忽略包装和展开,此处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

虽然第一种写作方式使得它可能更明确地传达了什么,第二种方式很好地展示了你如何不必关心它。

  1. 首先tick = do n <- get put (n + 1) return n 当前状态并在简化设置中公开getget :: s -> (s, s)表示您确实关心值并且您不想丢弃它,基础状态也会在后台传递而不会发生更改(这就是<-的工作方式)。

  2. 然后get,在将不必要的parens删除到put :: s -> (s -> ((), s))后等效,​​取一个值用(第一个参数)替换当前状态,并产生一个有状态的动作,其结果是您放弃的无趣价值put :: s -> s -> ((), s)(因为您没有使用()或因为您使用<-而不是>>)。由于>>=基础状态已更改为put,因此会将其传递。

  3. n + 1对基础状态没有任何作用,只有返回其参数。

  4. 总结一下,return以一些初始值tick开头,它在内部将其更新为s,并在旁边输出s+1

    另一个示例的工作原理完全相同,s仅用于放弃>>生成的()。但是状态一直都在传递。