Skip to content

Commit f6e81f2

Browse files
authored
More foldMap related Tree tests (#934)
* Test that last for Tree is lazy enough last should only need to look at the path going down to the last leaf. * Test foldMap and foldMap1 for Tree more Test that they fold according to the structure of the tree.
1 parent 67752b2 commit f6e81f2

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

containers-tests/tests/tree-properties.hs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ import Test.QuickCheck.Function (apply)
1111
import Test.QuickCheck.Poly (A, B, C, OrdA)
1212
import Control.Monad.Fix (MonadFix (..))
1313
import Control.Monad (ap)
14-
import Data.Foldable (foldl', toList)
14+
import Data.Foldable (fold, foldl', toList)
1515
import Data.Traversable (foldMapDefault)
16+
#if !(MIN_VERSION_base(4,11,0))
17+
import Data.Semigroup (Semigroup ((<>)))
18+
#endif
1619
#if MIN_VERSION_base(4,18,0)
20+
import Data.List.NonEmpty (NonEmpty (..))
1721
import qualified Data.List.NonEmpty as NE
1822
import qualified Data.Foldable1 as Foldable1
1923
#endif
@@ -32,6 +36,7 @@ main = defaultMain $ testGroup "tree-properties"
3236
, testProperty "monadFix_ls" prop_monadFix_ls
3337
, testProperty "toList" prop_toList
3438
, testProperty "foldMap" prop_foldMap
39+
, testProperty "foldMap_structure" prop_foldMap_structure
3540
, testProperty "foldl'" prop_foldl'
3641
, testProperty "foldr1" prop_foldr1
3742
, testProperty "foldl1" prop_foldl1
@@ -41,8 +46,10 @@ main = defaultMain $ testGroup "tree-properties"
4146
, testProperty "sum" prop_sum
4247
, testProperty "product" prop_product
4348
#if MIN_VERSION_base(4,18,0)
49+
, testProperty "foldMap1_structure" prop_foldMap1_structure
4450
, testProperty "toNonEmpty" prop_toNonEmpty
4551
, testProperty "last" prop_last
52+
, testProperty "last_path" prop_last_path
4653
, testProperty "foldrMap1" prop_foldrMap1
4754
, testProperty "foldlMap1'" prop_foldlMap1'
4855
, testProperty "foldlMap1" prop_foldlMap1
@@ -86,6 +93,25 @@ data Magma a
8693
| Magma a :* Magma a
8794
deriving (Eq, Show)
8895

96+
-- Unlawful on purpose.
97+
instance Semigroup (Magma a) where
98+
(<>) = (:*)
99+
100+
data UnitalMagma a
101+
= Unit
102+
| UInj a
103+
| UnitalMagma a :** UnitalMagma a
104+
deriving (Eq, Show)
105+
106+
-- Unlawful on purpose.
107+
instance Semigroup (UnitalMagma a) where
108+
(<>) = (:**)
109+
110+
-- Unlawful on purpose.
111+
instance Monoid (UnitalMagma a) where
112+
mempty = Unit
113+
mappend = (<>)
114+
89115
----------------------------------------------------------------
90116
-- Unit tests
91117
----------------------------------------------------------------
@@ -150,6 +176,14 @@ prop_foldMap t =
150176
foldMap (:[]) t === toList t .&&.
151177
foldMap (:[]) t === foldMapDefault (:[]) t
152178

179+
-- We use UnitalMagma with foldMap to test that the structure of the fold
180+
-- follows that of the tree. This is desirable here because we can be more
181+
-- efficient/lazy with some monoids, such as Data.Monoid.Last, compared
182+
-- to a foldr-based foldMap.
183+
prop_foldMap_structure :: Tree A -> Property
184+
prop_foldMap_structure t =
185+
foldMap UInj t === foldTree (\x ys -> fold (UInj x : ys)) t
186+
153187
prop_foldl' :: Tree A -> Property
154188
prop_foldl' t = foldl' (flip (:)) [] t === reverse (toList t)
155189

@@ -179,12 +213,32 @@ prop_product :: Tree OrdA -> Property
179213
prop_product t = product t === product (toList t)
180214

181215
#if MIN_VERSION_base(4,18,0)
216+
-- We use Magma with foldMap1 to test that the structure of the fold follows
217+
-- that of the tree. This is desirable here because we can be more
218+
-- efficient/lazy with some semigroups, such as Data.Semigroup.Last, compared
219+
-- to a foldrMap1-based foldMap1.
220+
prop_foldMap1_structure :: Tree A -> Property
221+
prop_foldMap1_structure t =
222+
Foldable1.foldMap1 Inj t === foldTree (\x ys -> Foldable1.fold1 (Inj x :| ys)) t
223+
182224
prop_toNonEmpty :: Tree A -> Property
183225
prop_toNonEmpty t = Foldable1.toNonEmpty t === NE.fromList (toList t)
184226

185227
prop_last :: Tree A -> Property
186228
prop_last t = Foldable1.last t === NE.last (Foldable1.toNonEmpty t)
187229

230+
-- Tests that last only looks at the path going down to the last leaf.
231+
prop_last_path :: Tree A -> Property
232+
prop_last_path t = Foldable1.last (replace t) === Foldable1.last t
233+
where
234+
-- Replace all trees with bottom except for the last one.
235+
replace :: Tree a -> Tree a
236+
replace (Node x ts) = Node x (replaces ts)
237+
replaces :: [Tree a] -> [Tree a]
238+
replaces [] = []
239+
replaces [t] = [replace t]
240+
replaces (t:ts) = error "error tree" : replaces ts
241+
188242
prop_foldrMap1 :: Tree A -> Property
189243
prop_foldrMap1 t =
190244
Foldable1.foldrMap1 Inj f t === Foldable1.foldrMap1 Inj f (Foldable1.toNonEmpty t)

containers/src/Data/Tree.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,9 @@ unfoldForestQ f aQ = case viewl aQ of
552552
--
553553
-- Implemented:
554554
--
555-
-- foldrMap1, foldlMap1': Basic functions
556-
-- foldMap, foldMap1': Implemented same as the default definition, but
557-
-- INLINABLE to allow specialization.
555+
-- foldMap, foldrMap1, foldlMap1': Basic functions
556+
-- foldMap1': Implemented same as the default definition, but INLINABLE to
557+
-- allow specialization.
558558
-- toNonEmpty, foldlMap1: Implemented more efficiently than default.
559559
-- maximum, minimum: Uses Foldable's implementation.
560560
--

0 commit comments

Comments
 (0)