ここでリストの融合が機能しない問題は、実際にはかなり微妙です。RULE
リストを融合する権利を定義するとしましょう:
import GHC.Base
sum2 :: Num a => [a] -> a
sum2 = sum
{-# NOINLINE [1] sum2 #-}
{-# RULES "sum" forall (f :: forall b. (a->b->b)->b->b).
sum2 (build f) = f (+) 0 #-}
(簡単な説明はsum2
、 のエイリアスとして定義し、sum
GHC が早期にインライン化することを禁止するため、が削除さRULE
れる前に起動する可能性があるということです。次に、リストビルダーのすぐ隣sum2
を探し(定義を参照)、それを置き換えます)。直接演算による。)sum2
build
次のコアが生成されるため、これはさまざまな成功を収めています。
Main.$wgo =
\ (w_s1T4 :: GHC.Prim.Int#) ->
case GHC.Prim.remInt# w_s1T4 2 of _ {
__DEFAULT ->
case w_s1T4 of wild_Xg {
__DEFAULT -> Main.$wgo (GHC.Prim.+# wild_Xg 1);
15000000 -> 0
};
0 ->
case w_s1T4 of wild_Xg {
__DEFAULT ->
case Main.$wgo (GHC.Prim.+# wild_Xg 1) of ww_s1T7 { __DEFAULT ->
GHC.Prim.+# wild_Xg ww_s1T7
};
15000000 -> 15000000
}
}
$wgo
これは素晴らしい、完全に融合されたコードです -非テールコール位置での呼び出しがあるという唯一の問題があります。これは、ループを見ているのではなく、実際にはプログラムの結果が予測可能な、深く再帰的な関数を見ていることを意味します。
Stack space overflow: current size 8388608 bytes.
ここでの根本的な問題は、Prelude のリスト フュージョンが右フォールドのみを融合できることであり、合計を右フォールドとして計算すると、過度のスタック消費が直接発生します。明らかな修正は、実際にフュージョンを実装するDuncan のstream-fusion パッケージなど、実際に左折畳みを処理できるフュージョン フレームワークを使用することsum
です。
別の解決策は、それをハックして、右折りを使用して左折りを実装することです。
main = print $ foldr (\x c -> c . (+x)) id [2,4..15000000] 0
これにより、現在のバージョンの GHC で実際にほぼ完璧なコードが生成されます。一方、部分的に適用された関数を排除するのに十分スマートなGHCに依存しているため、これは一般的に良い考えではありません。すでにfilter
チェーンに a を追加すると、その特定の最適化が中断されます。