哈斯克尔国家monad逝世的混乱

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)

非常感谢!

jak*_*iel 5

它真的归结为理解状态是同构的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)

你可以忽略包装和使用展开StateTrunStateT,这里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)

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

  1. 首先get当前状态并公开它(get :: s -> (s, s)在一个简化的设置中),<-表示你关心这个值并且你不想扔掉它,底层状态也在后台传递而没有改变(这就是get工作原理) ).

  2. 然后put :: s -> (s -> ((), s)),在删除不必要的parens后等效put :: s -> s -> ((), s),取一个值用(第一个参数)替换当前状态,并产生一个有状态动作,其结果是()你丢弃的无趣值(因为你不使用<-或因为你使用>>而不是>>=).由于put基础状态已经改变,n + 1因此它被传递.

  3. return对底层状态没有任何作用,它只返回它的参数.

总而言之,tick从一些初始值开始,s它将其更新为s+1内部,并s在侧面输出.

另一个例子的工作方式完全相同,>>只是在那里用来扔掉所()产生的put.但状态一直在传递.