您可以使用 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 模式!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))
在存在尾调用优化的情况下,第一个伪代码等效于第二个伪代码(实际上是如何until
实现的) ,这就是为什么在存在 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 的答案中的流处理代码是如何产生的,
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
在纯 FP中,流处理范式使计算的所有历史都可用,作为值的流(列表)。