我一点一点地学习一些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 回答
大多数monad都带有一些原始的"run"操作,例如
runState
,execState
等 . 如果你经常在状态monad中调用runState
,这意味着你并没有真正使用monad提供的功能 . 你写您不必显式传递状态 . 这就是州monad所做的!你可以写
否则,该功能看起来合理 . 由于
a
是'if'两个分支的结果的一部分,我建议将其分解为清晰 .当我第一次开始编写monadic代码时,你犯的错误与我做的完全相同 - 过于复杂,过度使用
liftM
并且使用>>=
(等效地,使用了<-
符号) .理想情况下,您根本不必在州monad中提及
runState
或evalState
. 您想要的功能如下:读取当前状态
如果它满足谓词
f
,则返回如果不是,则运行计算
s
并将其结果添加到输出中你可以直接这样做:
请注意,如果它们属于同一个monad,则可以嵌套do语句!这非常有用 - 它允许您在一个do块中进行分支,只要if语句的两个分支都导致相同的monadic类型 .
此函数的推断类型是:
如果您愿意,可以将其专门用于
State s
类型,但您不必:如果你想稍后使用不同的monad,保持更一般的状态可能更为可取 .
直觉是什么?
只要
s
是有状态计算并且你在状态monad中,你就可以做到和
x
现在将具有计算结果(就像您调用evalState
并以初始状态进给) . 如果您需要检查状态,您可以这样做和
s'
将具有当前状态的值 .对于这么简单的任务,我不会使用
State
monad . 其他人已经澄清了你实际上应该如何编写monadic版本,但我想添加我的个人(更简单)的解决方案,因为你要求最惯用的方式来编写它 .或者,如果您只想要
collectUntil
,那么只需以下一行即可这里takeWhile和iterate来自Prelude . 为了完整性,因为它是实现的核心,以下是迭代的(非常简单的)代码:
warning :可能这不是't clear enough from my answer, but this solution isn' t真的一样,因为我融合了状态和结果在
State
之外工作 . 当然,通过使用f :: (s, a) -> (s, a)
然后使用map fst
或map snd
投影来分别得到中间状态或结果列表,可以做一些非常相似的事情 . 为了便于记法,尽管使用State
解决方案可能更简单 .