我一点一点地学习一些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]
这是否是这项任务的合理功能,还是有更惯用的方式?
答案 0 :(得分:9)
你犯的错误与我在第一次开始编写monadic代码时犯的错误相同 - 过于复杂,过度使用liftM
并且使用>>=
(等效地使用了<-
符号)。
理想情况下,您根本不必在州monad中提及runState
或evalState
。您想要的功能如下:
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都有一些原始的“运行”操作,例如runState
,execState
等等。如果您经常在状态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
此处takeWhile和iterate来自Prelude。为了完整性,因为它是实现的核心,以下是迭代的(非常简单的)代码:
iterate f x = x : iterate f (f x)
警告:从我的回答中可能这还不够清楚,但是这个解决方案实际上并不相同,因为我通过在State
之外工作将状态和结果融合在一起。当然,通过使用f :: (s, a) -> (s, a)
然后使用map fst
或map snd
投影来分别得到中间状态或结果列表,可以做一些非常相似的事情。为了便于记法,使用State
的解决方案可能更简单。