在Haskell与州立大学合作

Dan*_*n T 6 monads haskell

我一点一点地学习一些Haskell并且(慢慢地)正在努力理解State monad,尝试编写一个重复State计算的函数,直到状态满足一些布尔测试并在列表中收集返回值以获得整体结果.我终于成功了:

collectUntil :: (s -> Bool) -> State s a -> State s [a]
collectUntil f s = do s0 <- get
                      let (a,s') = runState s s0
                      put s'
                      if (f s') then return [a] else liftM (a:) $ collectUntil f s
Run Code Online (Sandbox Code Playgroud)

以便

simpleState = state (\x -> (x,x+1))

*Main> evalState (collectUntil (>10) simpleState) 0
[0,1,2,3,4,5,6,7,8,9,10]
Run Code Online (Sandbox Code Playgroud)

这是否是这项任务的合理功能,还是有更惯用的方式?

Chr*_*lor 9

当我第一次开始编写monadic代码时,你犯的错误与我所做的完全相同 - 使它过于复杂,过度使用liftM和使用不足>>=(相当于使用<-符号).

理想情况下,您根本不需要提及州内monad runStateevalState在州内monad.您想要的功能如下:

  • 阅读当前状态
  • 如果它满足谓词f,那么返回
  • 如果没有,则运行计算s并将其结果添加到输出

你可以直接这样做:

collectUntil f comp = do
    s <- get                              -- Get the current state
    if f s then return []                 -- If it satisfies predicate, return
           else do                        -- Otherwise...
               x  <- comp                 -- Perform the computation s
               xs <- collectUntil f comp  -- Perform the rest of the computation
               return (x:xs)              -- Collect the results and return them
Run Code Online (Sandbox Code Playgroud)

请注意,如果它们属于同一个monad,则可以嵌套do语句!这非常有用 - 它允许您在一个do块中进行分支,只要if语句的两个分支都导致相同的monadic类型.

此函数的推断类型是:

collectUntil :: MonadState t m => (t -> Bool) -> m a -> m [a]
Run Code Online (Sandbox Code Playgroud)

如果您愿意,可以将其专门化为该State s类型,但您不必:

collectUntil :: (s -> Bool) -> State s a -> State s [a]
Run Code Online (Sandbox Code Playgroud)

如果你想稍后使用不同的monad,保持更一般的状态可能更为可取.

什么是直觉?

每当s进行有状态计算并且你在状态monad中时,你就可以做到

x <- s
Run Code Online (Sandbox Code Playgroud)

x现在将有计算的结果(如同您叫evalState并且在初始状态下供给).如果您需要检查状态,您可以这样做

s' <- get
Run Code Online (Sandbox Code Playgroud)

并且s'将当前状态的值.