您可以使用 Haskell Prelude 函数 until :: (a -> Bool) -> (a -> a) -> a -> a 几乎自然地对循环进行编码:
g :: Int -> Float -> Float -> Float -> Float
g n a p s =
fst.snd $
until ((<= 0).fst)
(\(n,(!s,!p)) -> (n-1, (if even n then s+p else s-p, p*a)))
(n,(s,p))
bang-patterns !s 和 !p 标记严格计算的中间变量,以防止过度懒惰,否则会损害效率。
until pred step start 重复应用step 函数,直到使用最后生成的值调用的pred 将保持,从初始值start 开始。可以用伪代码来表示:
def until (pred, step, start): // well, actually,
while( true ): def until (pred, step, start):
if pred(start): return(start) if pred(start): return(start)
start := step(start) call until(pred, step, step(start))
在存在tail call optimization 的情况下,第一个伪代码等同于第二个伪代码(until 是actually implemented),这就是为什么在存在 TCO 的许多函数式语言中,循环是通过递归编码的。
所以在 Haskell 中,until 被编码为
until p f x | p x = x
| otherwise = until p f (f x)
但它可能有不同的编码,明确了中间结果:
until p f x = last $ go x -- or, last (go x)
where go x | p x = [x]
| otherwise = x : go (f x)
使用 Haskell 标准高阶函数 break 和 iterate 这可以写成流处理代码,
until p f x = let (_,(r:_)) = break p (iterate f x) in r
-- or: span (not.p) ....
或者只是
until p f x = head $ dropWhile (not.p) $ iterate f x -- or, equivalently,
-- head . dropWhile (not.p) . iterate f $ x
如果给定的 Haskell 实现中不存在 TCO,则将使用最后一个版本。
希望这可以更清楚地说明来自Daniel Wagner's answer 的流处理代码是如何产生的,
g n a p s = s + (sum . take n . iterate (*(-a)) $ if odd n then (-p) else p)
因为所涉及的谓词是关于从n 开始倒计时,并且
fst . snd . head . dropWhile ((> 0).fst) $
iterate (\(n,(!s,!p)) -> (n-1, (if even n then s+p else s-p, p*a)))
(n,(s,p))
===
fst . snd . head . dropWhile ((> 0).fst) $
iterate (\(n,(!s,!p)) -> (n-1, (s+p, p*(-a))))
(n,(s, if odd n then (-p) else p)) -- 0 is even
===
fst . (!! n) $
iterate (\(!s,!p) -> (s+p, p*(-a)))
(s, if odd n then (-p) else p)
===
foldl' (+) s . take n . iterate (*(-a)) $ if odd n then (-p) else p
在pureFP 中,流处理范例使所有计算历史都可用,作为值的流(列表)。