summaryrefslogtreecommitdiff
path: root/libraries
diff options
context:
space:
mode:
authorDavid Feuer <David.Feuer@gmail.com>2014-11-13 09:10:57 +0100
committerHerbert Valerio Riedel <hvr@gnu.org>2014-11-13 09:16:49 +0100
commit212a350547e950cc5be465a3d76e346ef14bf2ab (patch)
tree61453d586c8ed748522fa4c62b51a0fbcf01ba6a /libraries
parentc016e6f74e26708586352fec657798f271b0675b (diff)
downloadhaskell-212a350547e950cc5be465a3d76e346ef14bf2ab.tar.gz
Improve `Foldable` instance for `Array`
Previously, `Array`s were simply converted to lists, and the list methods used. That works acceptably well for `foldr` and `foldr1`, but not so sensibly for most other things. Left folds ended up "twisted" the way they are for lists, leading to surprising performance characteristics. Moreover, this implements `length` and `null` so they check the array size directly. Finally, a test is added to the testsuite ensuring the overridden `Foldable` methods agree with their expected default semantics. Addresses #9763 Reviewed By: hvr, austin Differential Revision: https://phabricator.haskell.org/D459
Diffstat (limited to 'libraries')
-rw-r--r--libraries/base/Data/Foldable.hs18
-rw-r--r--libraries/base/GHC/Arr.hs59
-rw-r--r--libraries/base/tests/all.T1
-rw-r--r--libraries/base/tests/foldableArray.hs129
-rw-r--r--libraries/base/tests/foldableArray.stdout13
5 files changed, 214 insertions, 6 deletions
diff --git a/libraries/base/Data/Foldable.hs b/libraries/base/Data/Foldable.hs
index 8d31b9a4de..8ad8c2fe26 100644
--- a/libraries/base/Data/Foldable.hs
+++ b/libraries/base/Data/Foldable.hs
@@ -56,7 +56,10 @@ import Data.Monoid
import Data.Ord
import Data.Proxy
-import GHC.Arr ( Array(..), Ix(..), elems )
+import GHC.Arr ( Array(..), Ix(..), elems, numElements,
+ foldlElems, foldrElems,
+ foldlElems', foldrElems',
+ foldl1Elems, foldr1Elems)
import GHC.Base hiding ( foldr )
import GHC.Num ( Num(..) )
@@ -252,10 +255,15 @@ instance Foldable ((,) a) where
foldr f z (_, y) = f y z
instance Ix i => Foldable (Array i) where
- foldr f z = List.foldr f z . elems
- foldl f z = List.foldl f z . elems
- foldr1 f = List.foldr1 f . elems
- foldl1 f = List.foldl1 f . elems
+ foldr = foldrElems
+ foldl = foldlElems
+ foldl' = foldlElems'
+ foldr' = foldrElems'
+ foldl1 = foldl1Elems
+ foldr1 = foldr1Elems
+ toList = elems
+ length = numElements
+ null a = numElements a == 0
instance Foldable Proxy where
foldMap _ _ = mempty
diff --git a/libraries/base/GHC/Arr.hs b/libraries/base/GHC/Arr.hs
index 0d5099366b..67702ea884 100644
--- a/libraries/base/GHC/Arr.hs
+++ b/libraries/base/GHC/Arr.hs
@@ -1,6 +1,5 @@
{-# LANGUAGE Unsafe #-}
{-# LANGUAGE NoImplicitPrelude, MagicHash, UnboxedTuples #-}
-{-# OPTIONS_GHC -funbox-strict-fields #-}
{-# OPTIONS_HADDOCK hide #-}
-----------------------------------------------------------------------------
@@ -30,6 +29,8 @@ module GHC.Arr (
newSTArray, boundsSTArray,
readSTArray, writeSTArray,
freezeSTArray, thawSTArray,
+ foldlElems, foldlElems', foldl1Elems,
+ foldrElems, foldrElems', foldr1Elems,
-- * Unsafe operations
fill, done,
@@ -557,6 +558,62 @@ elems :: Ix i => Array i e -> [e]
elems arr@(Array _ _ n _) =
[unsafeAt arr i | i <- [0 .. n - 1]]
+-- | A right fold over the elements
+{-# INLINABLE foldrElems #-}
+foldrElems :: Ix i => (a -> b -> b) -> b -> Array i a -> b
+foldrElems f b0 = \ arr@(Array _ _ n _) ->
+ let
+ go i | i == n = b0
+ | otherwise = f (unsafeAt arr i) (go (i+1))
+ in go 0
+
+-- | A left fold over the elements
+{-# INLINABLE foldlElems #-}
+foldlElems :: Ix i => (b -> a -> b) -> b -> Array i a -> b
+foldlElems f b0 = \ arr@(Array _ _ n _) ->
+ let
+ go i | i == (-1) = b0
+ | otherwise = f (go (i-1)) (unsafeAt arr i)
+ in go (n-1)
+
+-- | A strict right fold over the elements
+{-# INLINABLE foldrElems' #-}
+foldrElems' :: Ix i => (a -> b -> b) -> b -> Array i a -> b
+foldrElems' f b0 = \ arr@(Array _ _ n _) ->
+ let
+ go i a | i == (-1) = a
+ | otherwise = go (i-1) (f (unsafeAt arr i) $! a)
+ in go (n-1) b0
+
+-- | A strict left fold over the elements
+{-# INLINABLE foldlElems' #-}
+foldlElems' :: Ix i => (b -> a -> b) -> b -> Array i a -> b
+foldlElems' f b0 = \ arr@(Array _ _ n _) ->
+ let
+ go i a | i == n = a
+ | otherwise = go (i+1) (a `seq` f a (unsafeAt arr i))
+ in go 0 b0
+
+-- | A left fold over the elements with no starting value
+{-# INLINABLE foldl1Elems #-}
+foldl1Elems :: Ix i => (a -> a -> a) -> Array i a -> a
+foldl1Elems f = \ arr@(Array _ _ n _) ->
+ let
+ go i | i == 0 = unsafeAt arr 0
+ | otherwise = f (go (i-1)) (unsafeAt arr i)
+ in
+ if n == 0 then error "foldl1: empty Array" else go (n-1)
+
+-- | A right fold over the elements with no starting value
+{-# INLINABLE foldr1Elems #-}
+foldr1Elems :: Ix i => (a -> a -> a) -> Array i a -> a
+foldr1Elems f = \ arr@(Array _ _ n _) ->
+ let
+ go i | i == n-1 = unsafeAt arr i
+ | otherwise = f (unsafeAt arr i) (go (i + 1))
+ in
+ if n == 0 then error "foldr1: empty Array" else go 0
+
-- | The list of associations of an array in index order.
{-# INLINE assocs #-}
assocs :: Ix i => Array i e -> [(i, e)]
diff --git a/libraries/base/tests/all.T b/libraries/base/tests/all.T
index d4005b7d1e..fa8ecd3d47 100644
--- a/libraries/base/tests/all.T
+++ b/libraries/base/tests/all.T
@@ -83,6 +83,7 @@ test('enum03', when(fast(), skip), compile_and_run, ['-cpp'])
test('enum04', normal, compile_and_run, [''])
test('exceptionsrun001', normal, compile_and_run, [''])
test('exceptionsrun002', normal, compile_and_run, [''])
+test('foldableArray', normal, compile_and_run, [''])
test('list001' , when(fast(), skip), compile_and_run, [''])
test('list002', when(fast(), skip), compile_and_run, [''])
test('list003', when(fast(), skip), compile_and_run, [''])
diff --git a/libraries/base/tests/foldableArray.hs b/libraries/base/tests/foldableArray.hs
new file mode 100644
index 0000000000..5a5041f102
--- /dev/null
+++ b/libraries/base/tests/foldableArray.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE CPP #-}
+module Main where
+import Prelude hiding (foldr, foldl, foldl', foldr1, foldl1, length, null, sum,
+ product, all, any, and, or)
+import Data.Foldable
+import Control.Exception
+import Data.Array
+import Data.Foldable
+import Data.Typeable
+import Data.Either
+import Control.Applicative
+import Control.DeepSeq
+#if __GLASGOW_HASKELL__ < 709
+import qualified Data.List as L
+#else
+import qualified Data.OldList as L
+#endif
+
+data BadElementException = BadFirst | BadLast deriving (Show, Typeable, Eq)
+instance Exception BadElementException
+
+newtype ForceDefault f a = ForceDefault (f a)
+instance Foldable f => Foldable (ForceDefault f) where
+ foldMap f (ForceDefault c) = foldMap f c
+
+goodLists, badFronts, badBacks :: [[Integer]]
+goodLists = [[0..n] | n <- [(-1)..5]]
+badFronts = map (throw BadFirst :) goodLists
+badBacks = map (++ [throw BadLast]) goodLists
+doubleBads = map (\l -> throw BadFirst : l ++ [throw BadLast]) goodLists
+lists =
+ goodLists
+ ++ badFronts
+ ++ badBacks
+ ++ doubleBads
+
+makeArray xs = array (1::Int, length xs) (zip [1..] xs)
+
+arrays = map makeArray lists
+goodArrays = map makeArray goodLists
+
+
+strictCons x y = x + 10*y
+rightLazyCons x y = x
+leftLazyCons x y = y
+
+conses :: [Integer -> Integer -> Integer]
+conses = [(+), strictCons, rightLazyCons, leftLazyCons]
+
+runOneRight :: forall f . Foldable f =>
+ (forall a b . (a -> b -> b) -> b -> f a -> b) ->
+ (Integer -> Integer -> Integer) -> f Integer ->
+ IO (Either BadElementException Integer)
+runOneRight fol f container = try (evaluate (fol f 12 container))
+
+runOne1 :: forall f . Foldable f => (forall a . (a -> a -> a) -> f a -> a) ->
+ (Integer -> Integer -> Integer) -> f Integer ->
+ IO (Either BadElementException Integer)
+runOne1 fol f container = try (evaluate (fol f container))
+
+runOneLeft :: forall f . Foldable f =>
+ (forall a b . (b -> a -> b) -> b -> f a -> b) ->
+ (Integer -> Integer -> Integer) -> f Integer ->
+ IO (Either BadElementException Integer)
+runOneLeft fol f container = try (evaluate (fol f 13 container))
+
+runWithAllRight :: forall f . Foldable f =>
+ (forall a b . (a -> b -> b) -> b -> f a -> b) ->
+ [f Integer] -> IO [Either BadElementException Integer]
+runWithAllRight fol containers =
+ mapM (uncurry (runOneRight fol)) [(f,c) | f <- conses, c <- containers]
+
+runWithAll1 :: forall f . Foldable f =>
+ (forall a . (a -> a -> a) -> f a -> a) ->
+ [f Integer] -> IO [Either BadElementException Integer]
+runWithAll1 fol containers =
+ mapM (uncurry (runOne1 fol)) [(f,c) | f <- conses, c <- containers]
+
+runWithAllLeft :: forall f . Foldable f =>
+ (forall a b . (b -> a -> b) -> b -> f a -> b) ->
+ [f Integer] -> IO [Either BadElementException Integer]
+runWithAllLeft fol containers = mapM (uncurry (runOneLeft fol))
+ [(f,c) | f <- map flip conses, c <- containers]
+
+testWithAllRight :: forall f . Foldable f =>
+ (forall a b . (a -> b -> b) -> b -> f a -> b) ->
+ (forall a b . (a -> b -> b) -> b -> ForceDefault f a -> b) ->
+ [f Integer] -> IO Bool
+testWithAllRight fol1 fol2 containers = (==) <$>
+ runWithAllRight fol1 containers <*>
+ runWithAllRight fol2 (map ForceDefault containers)
+
+testWithAllLeft :: forall f . Foldable f =>
+ (forall a b . (b -> a -> b) -> b -> f a -> b) ->
+ (forall a b . (b -> a -> b) -> b -> ForceDefault f a -> b) ->
+ [f Integer] -> IO Bool
+testWithAllLeft fol1 fol2 containers = (==) <$>
+ runWithAllLeft fol1 containers <*>
+ runWithAllLeft fol2 (map ForceDefault containers)
+
+
+testWithAll1 :: forall f . Foldable f =>
+ (forall a . (a -> a -> a) -> f a -> a) ->
+ (forall a . (a -> a -> a) -> ForceDefault f a -> a) ->
+ [f Integer] -> IO Bool
+testWithAll1 fol1 fol2 containers =
+ (==) <$> runWithAll1 fol1 containers
+ <*> runWithAll1 fol2 (map ForceDefault containers)
+
+checkup f g cs = map f cs == map g (map ForceDefault cs)
+
+main = do
+ testWithAllRight foldr foldr arrays >>= print
+ testWithAllRight foldr' foldr' arrays >>= print
+ testWithAllLeft foldl foldl arrays >>= print
+ testWithAllLeft foldl' foldl' arrays >>= print
+ testWithAll1 foldl1 foldl1 (filter (not . null) arrays) >>= print
+ testWithAll1 foldr1 foldr1 (filter (not . null) arrays) >>= print
+ -- we won't bother with the fancy laziness tests for the rest
+ print $ checkup length length goodArrays
+ print $ checkup sum sum goodArrays
+ print $ checkup product product goodArrays
+ print $ checkup maximum maximum $ filter (not . null) goodArrays
+ print $ checkup minimum minimum $ filter (not . null) goodArrays
+ print $ checkup toList toList goodArrays
+ print $ checkup null null arrays
diff --git a/libraries/base/tests/foldableArray.stdout b/libraries/base/tests/foldableArray.stdout
new file mode 100644
index 0000000000..50aa4a9638
--- /dev/null
+++ b/libraries/base/tests/foldableArray.stdout
@@ -0,0 +1,13 @@
+True
+True
+True
+True
+True
+True
+True
+True
+True
+True
+True
+True
+True