diff options
author | Simon Peyton Jones <simonpj@microsoft.com> | 2019-02-18 09:03:01 +0000 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2019-02-20 10:17:34 -0500 |
commit | 2209ea86cbdfb1e08772a41f74b28563119b4385 (patch) | |
tree | 5e449a86bc54aeff90e52cd5eab55369795270e9 /libraries/base | |
parent | 2e96ce1fced5ea4f005edea2eeab50f67acb9114 (diff) | |
download | haskell-2209ea86cbdfb1e08772a41f74b28563119b4385.tar.gz |
Add comments about how zip fusion
Alexandre Balde (rockbmb) points out that the fusion technology
for foldr2, zip, zipWith, etc is undocumented. This patch adds
comments to explain.
Diffstat (limited to 'libraries/base')
-rw-r--r-- | libraries/base/Control/Monad.hs | 4 | ||||
-rw-r--r-- | libraries/base/GHC/List.hs | 87 |
2 files changed, 65 insertions, 26 deletions
diff --git a/libraries/base/Control/Monad.hs b/libraries/base/Control/Monad.hs index 96d8938101..75bc2b2db3 100644 --- a/libraries/base/Control/Monad.hs +++ b/libraries/base/Control/Monad.hs @@ -196,11 +196,15 @@ mapAndUnzipM f xs = unzip <$> traverse f xs -- | The 'zipWithM' function generalizes 'zipWith' to arbitrary applicative functors. zipWithM :: (Applicative m) => (a -> b -> m c) -> [a] -> [b] -> m [c] {-# INLINE zipWithM #-} +-- Inline so that fusion with zipWith and sequenceA have a chance to fire +-- See Note [Fusion for zipN/zipWithN] in List.hs] zipWithM f xs ys = sequenceA (zipWith f xs ys) -- | 'zipWithM_' is the extension of 'zipWithM' which ignores the final result. zipWithM_ :: (Applicative m) => (a -> b -> m c) -> [a] -> [b] -> m () {-# INLINE zipWithM_ #-} +-- Inline so that fusion with zipWith and sequenceA have a chance to fire +-- See Note [Fusion for zipN/zipWithN] in List.hs] zipWithM_ f xs ys = sequenceA_ (zipWith f xs ys) {- | The 'foldM' function is analogous to 'Data.Foldable.foldl', except that its result is diff --git a/libraries/base/GHC/List.hs b/libraries/base/GHC/List.hs index df2c19a8e2..8f03ce3a08 100644 --- a/libraries/base/GHC/List.hs +++ b/libraries/base/GHC/List.hs @@ -925,14 +925,14 @@ foldr2 k z = go go [] _ys = z go _xs [] = z go (x:xs) (y:ys) = k x y (go xs ys) -{-# INLINE [0] foldr2 #-} +{-# INLINE [0] foldr2 #-} -- See Note [Fusion for foldrN] foldr2_left :: (a -> b -> c -> d) -> d -> a -> ([b] -> c) -> [b] -> d foldr2_left _k z _x _r [] = z foldr2_left k _z x r (y:ys) = k x y (r ys) -- foldr2 k z xs ys = foldr (foldr2_left k z) (\_ -> z) xs ys -{-# RULES +{-# RULES -- See Note [Fusion for foldrN] "foldr2/left" forall k z ys (g::forall b.(a->b->b)->b->b) . foldr2 k z (build g) ys = g (foldr2_left k z) (\_ -> z) ys #-} @@ -944,7 +944,7 @@ foldr3 k z = go go _ [] _ = z go _ _ [] = z go (a:as) (b:bs) (c:cs) = k a b c (go as bs cs) -{-# INLINE [0] foldr3 #-} +{-# INLINE [0] foldr3 #-} -- See Note [Fusion for foldrN] foldr3_left :: (a -> b -> c -> d -> e) -> e -> a -> @@ -953,28 +953,63 @@ foldr3_left k _z a r (b:bs) (c:cs) = k a b c (r bs cs) foldr3_left _ z _ _ _ _ = z -- foldr3 k n xs ys zs = foldr (foldr3_left k n) (\_ _ -> n) xs ys zs -{-# RULES +{-# RULES -- See Note [Fusion for foldrN] "foldr3/left" forall k z (g::forall b.(a->b->b)->b->b). foldr3 k z (build g) = g (foldr3_left k z) (\_ _ -> z) #-} --- There used to be a foldr2/right rule, allowing foldr2 to fuse with a build --- form on the right. However, this causes trouble if the right list ends in --- a bottom that is only avoided by the left list ending at that spot. That is, --- foldr2 f z [a,b,c] (d:e:f:_|_), where the right list is produced by a build --- form, would cause the foldr2/right rule to introduce bottom. Example: --- --- zip [1,2,3,4] (unfoldr (\s -> if s > 4 then undefined else Just (s,s+1)) 1) --- --- should produce --- --- [(1,1),(2,2),(3,3),(4,4)] --- --- but with the foldr2/right rule it would instead produce --- --- (1,1):(2,2):(3,3):(4,4):_|_ - --- Zips for larger tuples are in the List module. +{- Note [Fusion for foldrN] +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We arrange that foldr2, foldr3, etc is a good consumer for its first +(left) list argument. Here's how. See below for the second, third +etc list arguments + +* The rule "foldr2/left" (active only before phase 1) does this: + foldr2 k z (build g) ys = g (foldr2_left k z) (\_ -> z) ys + thereby fusing away the 'build' on the left argument + +* To ensure this rule has a chance to fire, foldr2 has a NOINLINE[1] pragma + +There used to be a "foldr2/right" rule, allowing foldr2 to fuse with a build +form on the right. However, this causes trouble if the right list ends in +a bottom that is only avoided by the left list ending at that spot. That is, +foldr2 f z [a,b,c] (d:e:f:_|_), where the right list is produced by a build +form, would cause the foldr2/right rule to introduce bottom. Example: + zip [1,2,3,4] (unfoldr (\s -> if s > 4 then undefined else Just (s,s+1)) 1) +should produce + [(1,1),(2,2),(3,3),(4,4)] +but with the foldr2/right rule it would instead produce + (1,1):(2,2):(3,3):(4,4):_|_ + +Note [Fusion for zipN/zipWithN] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We arrange that zip, zip3, etc, and zipWith, zipWit3 etc, are all +good consumers for their first (left) argument, and good producers. +Here's how. See Note [Fusion for foldr2] for why it can't fuse its +second (right) list argument. + +NB: Zips for larger tuples are in the List module. + +* Rule "zip" (active only before phase 1) rewrites + zip xs ys = build (\c n -> foldr2 (zipFB c) n xs ys) + See also Note [Inline FB functions] + + Ditto rule "zipWith". + +* To give this rule a chance to fire, we give zip a NOLINLINE[1] + pragma (although since zip is recursive it might not need it) + +* Now the rules for foldr2 (see Note [Fusion for foldr2]) may fire, + or rules that fuse the build-produced output of zip. + +* If none of these fire, rule "zipList" (active only in phase 1) + rewrites the foldr2 call back to zip + foldr2 (zipFB (:)) [] = zip + This rule will only fire when build has inlined, which also + happens in phase 1. + + Ditto rule "zipWithList". +-} ---------------------------------------------- -- | /O(min(m,n))/. 'zip' takes two lists and returns a list of corresponding @@ -995,7 +1030,7 @@ foldr3_left _ z _ _ _ _ = z -- -- 'zip' is capable of list fusion, but it is restricted to its -- first list argument and its resulting list. -{-# NOINLINE [1] zip #-} +{-# NOINLINE [1] zip #-} -- See Note [Fusion for zipN/zipWithN] zip :: [a] -> [b] -> [(a,b)] zip [] _bs = [] zip _as [] = [] @@ -1005,7 +1040,7 @@ zip (a:as) (b:bs) = (a,b) : zip as bs zipFB :: ((a, b) -> c -> d) -> a -> b -> c -> d zipFB c = \x y r -> (x,y) `c` r -{-# RULES +{-# RULES -- See Note [Fusion for zipN/zipWithN] "zip" [~1] forall xs ys. zip xs ys = build (\c n -> foldr2 (zipFB c) n xs ys) "zipList" [1] foldr2 (zipFB (:)) [] = zip #-} @@ -1026,7 +1061,7 @@ zip3 _ _ _ = [] zip3FB :: ((a,b,c) -> xs -> xs') -> a -> b -> c -> xs -> xs' zip3FB cons = \a b c r -> (a,b,c) `cons` r -{-# RULES +{-# RULES -- See Note [Fusion for zipN/zipWithN] "zip3" [~1] forall as bs cs. zip3 as bs cs = build (\c n -> foldr3 (zip3FB c) n as bs cs) "zip3List" [1] foldr3 (zip3FB (:)) [] = zip3 #-} @@ -1049,7 +1084,7 @@ zip3FB cons = \a b c r -> (a,b,c) `cons` r -- -- 'zipWith' is capable of list fusion, but it is restricted to its -- first list argument and its resulting list. -{-# NOINLINE [1] zipWith #-} +{-# NOINLINE [1] zipWith #-} -- See Note [Fusion for zipN/zipWithN] zipWith :: (a->b->c) -> [a]->[b]->[c] zipWith f = go where @@ -1063,7 +1098,7 @@ zipWith f = go zipWithFB :: (a -> b -> c) -> (d -> e -> a) -> d -> e -> b -> c zipWithFB c f = \x y r -> (x `f` y) `c` r -{-# RULES +{-# RULES -- See Note [Fusion for zipN/zipWithN] "zipWith" [~1] forall f xs ys. zipWith f xs ys = build (\c n -> foldr2 (zipWithFB c f) n xs ys) "zipWithList" [1] forall f. foldr2 (zipWithFB (:) f) [] = zipWith f #-} |