在Haskell与州立大学合作

时间:2012-06-28 17:57:58

标签: haskell monads

我一点一点地学习一些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

这样

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

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

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

3 个答案:

答案 0 :(得分:9)

你犯的错误与我在第一次开始编写monadic代码时犯的错误相同 - 过于复杂,过度使用liftM并且使用>>=(等效地使用了<-符号)。

理想情况下,您根本不必在州monad中提及runStateevalState。您想要的功能如下:

  • 阅读当前状态
  • 如果它满足谓词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

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

此功能的推断类型是:

collectUntil :: MonadState t m => (t -> Bool) -> m a -> m [a]

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

collectUntil :: (s -> Bool) -> State s a -> State s [a]

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

直觉是什么?

每当s是有状态计算并且你在状态monad中时,你就可以

x <- s

x现在将具有计算结果(就像您调用evalState并以初始状态进行输入一样)。如果您需要检查状态,可以执行

s' <- get

s'将具有当前状态的值。

答案 1 :(得分:4)

大多数monad都有一些原始的“运行”操作,例如runStateexecState等等。如果您经常在状态monad中调用runState ,则表示您并未真正使用monad提供的功能。你写过

s0 <- get                    -- Read state
let (a,s') = runState s s0   -- Pass state to 's', get new state
put s'                       -- Save new state

您不必明确传递状态。这就是州monad所做的!你可以写

a <- s

否则,该功能看起来合理。由于a是'if'两个分支中结果的一部分,因此我建议将其分解为清晰。

collectUntil f s = step
  where
    step = do a <- s
              liftM (a:) continue
    continue = do s' <- get
                  if f s' then return [] else step

答案 2 :(得分:2)

对于这样一个简单的任务,我不会使用State monad。其他人已经澄清了你实际上应该如何编写monadic版本,但是我想添加我的个人(更简单)的解决方案,因为你要求用最惯用的方式来编写它。

collectWhile, collectUntil :: (a -> a) -> (a -> Bool) -> a -> [a]
collectWhile f cond z = takeWhile cond $ iterate f z
collectUntil f cond z = collectWhile f (not . cond) z

或者,如果您只想要collectUntil

,只需以下一行即可
collectUntil f cond z = takeWhile (not.cond) $ iterate f z

此处takeWhileiterate来自Prelude。为了完整性,因为它是实现的核心,以下是迭代的(非常简单的)代码:

iterate f x =  x : iterate f (f x)

警告:从我的回答中可能这还不够清楚,但是这个解决方案实际上并不相同,因为我通过在State之外工作将状态和结果融合在一起。当然,通过使用f :: (s, a) -> (s, a)然后使用map fstmap snd投影来分别得到中间状态或结果列表,可以做一些非常相似的事情。为了便于记法,使用State的解决方案可能更简单。