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
mark は、厳密に計算された中間変数を使用して、効率を損なう過度の怠惰を防ぎます。
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))
最初の疑似コードは、テール コールの最適化が存在する場合の 2 番目の疑似コード (これが実際の実装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 が存在しない場合は、最後のバージョンが使用されます。
うまくいけば、これにより、ダニエル・ワグナーの回答からのストリーム処理コードがどのように発生するかが明確になります。
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では、ストリーム処理パラダイムにより、計算のすべての履歴が値のストリーム (リスト) として利用可能になります。