首页 文章

在Haskell与州立大学合作

提问于
浏览
6

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

  • 9

    大多数monad都带有一些原始的"run"操作,例如 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
    
  • 4

    当我第一次开始编写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' 将具有当前状态的值 .

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

    warning :可能这不是't clear enough from my answer, but this solution isn' t真的一样,因为我融合了状态和结果在 State 之外工作 . 当然,通过使用 f :: (s, a) -> (s, a) 然后使用 map fstmap snd 投影来分别得到中间状态或结果列表,可以做一些非常相似的事情 . 为了便于记法,尽管使用 State 解决方案可能更简单 .

相关问题