Skip to content

Commit e506e24

Browse files
committed
feat: Support DNonEmpty on older base versions
* Add CPP guards around some instances * Remove some conditional cabal * Use NonEmpty-based fold functions directly * Add tests for fold instance * Disable DNonEmpty test suite on base <4.9.0
1 parent bc5e551 commit e506e24

5 files changed

Lines changed: 121 additions & 49 deletions

File tree

Data/DList/DNonEmpty.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88

99
-----------------------------------------------------------------------------
1010

11+
-- GHC >=8 supports this flag
12+
#if MIN_VERSION_base(4,9,0)
1113
-- CPP: Ignore unused imports when Haddock is run
12-
#if defined(__HADDOCK_VERSION__)
14+
# if defined(__HADDOCK_VERSION__)
1315
{-# OPTIONS_GHC -Wno-unused-imports #-}
16+
# endif
1417
#endif
1518

1619
-----------------------------------------------------------------------------
@@ -41,8 +44,10 @@ module Data.DList.DNonEmpty
4144
DNonEmpty((:|)),
4245

4346
-- * Conversion
47+
#if MIN_VERSION_base(4,9,0)
4448
fromNonEmpty,
4549
toNonEmpty,
50+
#endif
4651
toList,
4752
fromList,
4853

@@ -64,7 +69,9 @@ import Data.DList.DNonEmpty.Internal
6469

6570
-- CPP: Import only for Haddock
6671
#if defined(__HADDOCK_VERSION__)
72+
# if MIN_VERSION_base(4,9,0)
6773
import Data.List.NonEmpty (NonEmpty)
74+
# endif
6875
import Data.DList (DList)
6976
#endif
7077

Data/DList/DNonEmpty/Internal.hs

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,14 @@ import Data.DList (DList)
5050
import qualified Data.DList as DList
5151
import qualified Data.Foldable as Foldable
5252
import Data.Function (on)
53+
#if !MIN_VERSION_base(4,8,0)
54+
import Data.Monoid (mappend)
55+
#endif
56+
#if MIN_VERSION_base(4,9,0)
5357
import Data.List.NonEmpty (NonEmpty)
5458
import qualified Data.List.NonEmpty as NonEmpty
5559
import qualified Data.Semigroup as Semigroup
60+
#endif
5661
import Data.String (IsString (..))
5762
import qualified GHC.Exts as Exts
5863
import qualified Text.Read as Read
@@ -123,9 +128,11 @@ More likely, you will convert from a 'NonEmpty', perform some operation on the
123128
-}
124129
{- ORMOLU_ENABLE -}
125130

131+
#if MIN_VERSION_base(4,9,0)
126132
{-# INLINE fromNonEmpty #-}
127133
fromNonEmpty :: NonEmpty a -> DNonEmpty a
128134
fromNonEmpty ~(x NonEmpty.:| xs) = x :| DList.fromList xs
135+
#endif
129136

130137
{- ORMOLU_DISABLE -}
131138
{-|
@@ -147,9 +154,11 @@ you achieved due to laziness in the construction.
147154
-}
148155
{- ORMOLU_ENABLE -}
149156

157+
#if MIN_VERSION_base(4,9,0)
150158
{-# INLINE toNonEmpty #-}
151159
toNonEmpty :: DNonEmpty a -> NonEmpty a
152160
toNonEmpty ~(x :| xs) = x NonEmpty.:| DList.toList xs
161+
#endif
153162

154163
{- ORMOLU_DISABLE -}
155164
{-|
@@ -378,23 +387,26 @@ map :: (a -> b) -> DNonEmpty a -> DNonEmpty b
378387
map f ~(x :| xs) = f x :| DList.map f xs
379388

380389
instance Eq a => Eq (DNonEmpty a) where
381-
(==) = (==) `on` toNonEmpty
390+
(==) = (==) `on` toList
382391

383392
instance Ord a => Ord (DNonEmpty a) where
384-
compare = compare `on` toNonEmpty
393+
compare = compare `on` toList
385394

386395
instance Read a => Read (DNonEmpty a) where
387396
readPrec = Read.parens $
388397
Read.prec 10 $ do
389398
Read.Ident "fromNonEmpty" <- Read.lexP
390-
dl <- Read.readPrec
391-
return $ fromNonEmpty dl
399+
Read.parens $ do
400+
x <- Read.prec 5 Read.readPrec
401+
Read.Symbol ":|" <- Read.lexP
402+
xs <- Read.prec 5 Read.readPrec
403+
return $ x :| DList.fromList xs
392404
readListPrec = Read.readListPrecDefault
393405

394406
instance Show a => Show (DNonEmpty a) where
395-
showsPrec p dl =
407+
showsPrec p (x :| xs)=
396408
showParen (p > 10) $
397-
showString "fromNonEmpty " . showsPrec 11 (toNonEmpty dl)
409+
showString "fromNonEmpty (" . showsPrec 5 x . showString " :| " . showsPrec 5 (DList.toList xs) . showString ")"
398410

399411
instance Functor DNonEmpty where
400412
{-# INLINE fmap #-}
@@ -416,36 +428,32 @@ instance Monad DNonEmpty where
416428
return = Applicative.pure
417429

418430
instance Foldable.Foldable DNonEmpty where
419-
{-# INLINE fold #-}
420-
fold = Foldable.fold . toNonEmpty
421-
422-
{-# INLINE foldMap #-}
423-
foldMap f = Foldable.foldMap f . toNonEmpty
424-
425-
{-# INLINE foldr #-}
426-
foldr f x = Foldable.foldr f x . toNonEmpty
427-
428-
{-# INLINE foldl #-}
429-
foldl f x = Foldable.foldl f x . toNonEmpty
431+
foldr f x = Foldable.foldr f x . toList
432+
foldl f x = Foldable.foldl f x . toList
430433

431-
{-# INLINE foldr1 #-}
432-
foldr1 f = Foldable.foldr1 f . toNonEmpty
433-
434-
{-# INLINE foldl1 #-}
435-
foldl1 f = Foldable.foldl1 f . toNonEmpty
436-
437-
{-# INLINE foldl' #-}
438-
foldl' f x = Foldable.foldl' f x . toNonEmpty
434+
#if MIN_VERSION_base(4,6,0)
435+
foldl' f x = Foldable.foldl' f x . toList
436+
foldr' f x = Foldable.foldr' f x . toList
437+
#endif
439438

440-
{-# INLINE foldr' #-}
441-
foldr' f x = Foldable.foldr' f x . toNonEmpty
439+
-- These are based on their NonEmpty counterparts
440+
-- We don't convert to NonEmpty, because we support
441+
-- base <4.9.0.0
442+
fold ~(x :| xs) = x `mappend` Foldable.fold xs
443+
foldMap f ~(x :| xs) = f x `mappend` Foldable.foldMap f xs
444+
foldr1 f (p :| ps) = Foldable.foldr go id ps p
445+
where
446+
go x r prev = f prev (r x)
447+
foldl1 f (x :| xs) = Foldable.foldl f x (DList.toList xs)
442448

449+
#if MIN_VERSION_base(4,8,0)
443450
{-# INLINE toList #-}
444451
toList = toList
452+
#endif
445453

446454
instance NFData a => NFData (DNonEmpty a) where
447455
{-# INLINE rnf #-}
448-
rnf = rnf . toNonEmpty
456+
rnf = rnf . toList
449457

450458
{-
451459
@@ -460,6 +468,7 @@ instance a ~ Char => IsString (DNonEmpty a) where
460468
{-# INLINE fromString #-}
461469
fromString = fromList
462470

471+
#if MIN_VERSION_base(4,7,0)
463472
instance Exts.IsList (DNonEmpty a) where
464473
type Item (DNonEmpty a) = a
465474

@@ -468,7 +477,10 @@ instance Exts.IsList (DNonEmpty a) where
468477

469478
{-# INLINE toList #-}
470479
toList = toList
480+
#endif
471481

482+
#if MIN_VERSION_base(4,9,0)
472483
instance Semigroup.Semigroup (DNonEmpty a) where
473484
{-# INLINE (<>) #-}
474485
(<>) = append
486+
#endif

dlist.cabal

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,9 @@ library
4949
deepseq >= 1.1 && < 1.6
5050
exposed-modules: Data.DList
5151
Data.DList.Unsafe
52+
Data.DList.DNonEmpty
5253
other-modules: Data.DList.Internal
53-
if impl(ghc >= 8.0)
54-
exposed-modules: Data.DList.DNonEmpty
55-
other-modules: Data.DList.DNonEmpty.Internal
54+
Data.DList.DNonEmpty.Internal
5655
default-language: Haskell2010
5756
default-extensions: TypeOperators
5857
ghc-options: -Wall
@@ -77,8 +76,7 @@ test-suite test
7776
other-modules: DListProperties
7877
OverloadedStrings
7978
QuickCheckUtil
80-
if impl(ghc >= 8.0)
81-
other-modules: DNonEmptyProperties
79+
DNonEmptyProperties
8280
hs-source-dirs: tests
8381
build-depends: dlist,
8482
base,

tests/DNonEmptyProperties.hs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{-# LANGUAGE CPP #-}
22

3-
-- CPP: GHC >= 7.8 for Safe Haskell
4-
#if __GLASGOW_HASKELL__ >= 708
5-
{-# LANGUAGE Safe #-}
6-
#endif
7-
3+
#if MIN_VERSION_base(4,9,0)
4+
{-# LANGUAGE DeriveFunctor #-}
5+
{-# LANGUAGE FlexibleContexts #-}
6+
{-# LANGUAGE Safe #-}
7+
{-# LANGUAGE TypeApplications #-}
8+
{-# OPTIONS_GHC -Wno-orphans #-}
89
--------------------------------------------------------------------------------
910

1011
-- | QuickCheck property tests for DNonEmpty.
@@ -22,11 +23,16 @@ import QuickCheckUtil
2223
import Test.QuickCheck
2324
import Text.Show.Functions ()
2425
import Prelude hiding (head, map, tail)
26+
import Data.Monoid (Sum)
27+
28+
-- NonEmpty.append was only added in base 4.16
29+
nonEmptyAppend :: NonEmpty a -> NonEmpty a -> NonEmpty a
30+
nonEmptyAppend (x NonEmpty.:| xs) ys = x NonEmpty.:| (xs ++ NonEmpty.toList ys)
2531

2632
--------------------------------------------------------------------------------
2733

28-
prop_model :: NonEmpty Int -> Bool
29-
prop_model = eqWith id (toNonEmpty . fromNonEmpty)
34+
prop_model :: DNonEmpty Int -> Bool
35+
prop_model = eqWith id id
3036

3137
prop_singleton :: Int -> Bool
3238
prop_singleton = eqWith Applicative.pure (toNonEmpty . singleton)
@@ -36,18 +42,30 @@ prop_cons c = eqWith (NonEmpty.cons c) (toNonEmpty . cons c . fromNonEmpty)
3642

3743
prop_snoc :: NonEmpty Int -> Int -> Bool
3844
prop_snoc xs c =
39-
xs Semigroup.<> Applicative.pure c == toNonEmpty (snoc (fromNonEmpty xs) c)
45+
xs `nonEmptyAppend` Applicative.pure c == toNonEmpty (snoc (fromNonEmpty xs) c)
4046

4147
prop_append :: NonEmpty Int -> NonEmpty Int -> Bool
4248
prop_append xs ys =
43-
xs Semigroup.<> ys == toNonEmpty (fromNonEmpty xs `append` fromNonEmpty ys)
49+
xs `nonEmptyAppend` ys == toNonEmpty (fromNonEmpty xs `append` fromNonEmpty ys)
4450

4551
prop_head :: NonEmpty Int -> Bool
4652
prop_head = eqWith NonEmpty.head (head . fromNonEmpty)
4753

4854
prop_tail :: NonEmpty Int -> Bool
4955
prop_tail = eqWith NonEmpty.tail (DList.toList . tail . fromNonEmpty)
5056

57+
prop_foldr :: Eq b => (a -> b -> b) -> b -> NonEmpty a -> Bool
58+
prop_foldr f initial l = foldr f initial l == foldr f initial (fromNonEmpty l)
59+
60+
prop_foldr1 :: Eq a => (a -> a -> a) -> NonEmpty a -> Bool
61+
prop_foldr1 f l = foldr1 f l == foldr1 f (fromNonEmpty l)
62+
63+
prop_foldl :: Eq b => (b -> a -> b) -> b -> NonEmpty a -> Bool
64+
prop_foldl f initial l = foldl f initial l == foldl f initial (fromNonEmpty l)
65+
66+
prop_foldMap :: (Eq b, Monoid b) => (a -> b) -> NonEmpty a -> Bool
67+
prop_foldMap f l = foldMap f l == foldMap f (fromNonEmpty l)
68+
5169
prop_unfoldr :: (Int -> (Int, Maybe Int)) -> Int -> Int -> Property
5270
prop_unfoldr f n =
5371
eqOn
@@ -61,6 +79,14 @@ prop_map f = eqWith (NonEmpty.map f) (toNonEmpty . map f . fromNonEmpty)
6179
prop_show_read :: NonEmpty Int -> Bool
6280
prop_show_read = eqWith id (read . show) . fromNonEmpty
6381

82+
prop_inner_show_read ::
83+
( Eq (f (DNonEmpty a))
84+
, Show (f (DNonEmpty a))
85+
, Read (f (DNonEmpty a))
86+
, Functor f
87+
) => f (NonEmpty a) -> Bool
88+
prop_inner_show_read = eqWith id (read . show) . fmap fromNonEmpty
89+
6490
prop_read_show :: NonEmpty Int -> Bool
6591
prop_read_show x = eqWith id (show . f . read) $ "fromNonEmpty (" ++ show x ++ ")"
6692
where
@@ -87,6 +113,21 @@ prop_Semigroup_append xs ys =
87113

88114
--------------------------------------------------------------------------------
89115

116+
newtype Single a = Single a
117+
deriving (Eq, Read, Show, Functor)
118+
119+
instance Arbitrary a => Arbitrary (Single a) where
120+
arbitrary = Single <$> arbitrary
121+
122+
instance Arbitrary a => Arbitrary (DList.DList a) where
123+
arbitrary = DList.fromList <$> arbitrary
124+
125+
instance Arbitrary a => Arbitrary (DNonEmpty a) where
126+
arbitrary = do
127+
x <- arbitrary
128+
xs <- arbitrary
129+
pure $ x :| xs
130+
90131
properties :: [(String, Property)]
91132
properties =
92133
[ ("model", property prop_model),
@@ -97,10 +138,30 @@ properties =
97138
("head", property prop_head),
98139
("tail", property prop_tail),
99140
("unfoldr", property prop_unfoldr),
141+
("foldr", property (prop_foldr @Int @Int)),
142+
("foldr1", property (prop_foldr1 @Int)),
143+
("foldl", property (prop_foldl @Int @Int)),
144+
("foldMap", property (prop_foldMap @(Sum Int) @Int)),
100145
("map", property prop_map),
101146
("read . show", property prop_show_read),
147+
("read . show", property (prop_inner_show_read @Single @Int)),
148+
("read . show", property (prop_inner_show_read @((,) Int) @(Int, Int))),
149+
("read . show", property (prop_inner_show_read @Single @(DNonEmpty Int))),
102150
("show . read", property prop_read_show),
103151
("toList", property prop_toList),
104152
("fromList", property prop_fromList),
105153
("Semigroup <>", property prop_Semigroup_append)
106154
]
155+
156+
#else
157+
158+
#warning Skipping DNonEmptyProperties tests due to old version of base
159+
160+
module DNonEmptyProperties (properties) where
161+
162+
import Test.QuickCheck
163+
164+
properties :: [(String, Property)]
165+
properties = []
166+
167+
#endif

tests/Main.hs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ module Main (main) where
1313
--------------------------------------------------------------------------------
1414

1515
import qualified DListProperties
16-
-- CPP: GHC >= 8 for DNonEmpty
17-
#if __GLASGOW_HASKELL__ >= 800
1816
import qualified DNonEmptyProperties
19-
#endif
2017
import qualified OverloadedStrings
2118
import QuickCheckUtil (quickCheckLabeledProperties)
2219
import Control.Monad (unless)
@@ -30,8 +27,5 @@ main = do
3027
OverloadedStrings.test
3128
result <- quickCheckLabeledProperties $
3229
DListProperties.properties
33-
-- CPP: GHC >= 8 for DNonEmpty
34-
#if __GLASGOW_HASKELL__ >= 800
3530
++ DNonEmptyProperties.properties
36-
#endif
3731
unless (isSuccess result) exitFailure

0 commit comments

Comments
 (0)