Skip to content

Commit f85d281

Browse files
committed
General improvements
* Remove unnecessary constraints from `Data.BitSet.Generic`. * Use a more streamlined implementation of `foldl'` and `foldr'` when possible. * Update Cabal file to indicate required extensions. * Make the tests work again.
1 parent bf70326 commit f85d281

File tree

6 files changed

+170
-50
lines changed

6 files changed

+170
-50
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
cabal-dev
22
dist
3+
.cabal-sandbox/
4+
cabal.sandbox.config
5+
.stack-work/
6+
*.hi
7+
*.o
38

49
# This file will be autogenerated on 'cabal build'.
510
cbits/GmpDerivedConstants.h

bitset.cabal

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ Bug-reports: http://github.com/lambda-llama/bitset/issues
1616
Stability: Experimental
1717
Cabal-Version: >= 1.12
1818
Build-type: Custom
19-
Tested-with: GHC >= 7.4.1
20-
Extra-Source-Files: bin/mkDerivedGmpConstants.c
19+
Tested-with: GHC == 7.4.1, GHC == 7.6.3, GHC == 7.8.4
20+
Extra-Source-Files: bin/mkDerivedGmpConstants.c, include/bitset.h
2121

2222
Source-repository head
2323
Type: git
@@ -27,9 +27,15 @@ Library
2727
Hs-source-dirs: src
2828
Ghc-options: -Wall -fno-warn-orphans
2929
Default-language: Haskell2010
30+
Other-extensions: CPP, NamedFieldPuns, MagicHash, UnboxedTuples,
31+
BangPatterns, ForeignFunctionInterface,
32+
GHCForeignImportPrim, MagicHash,
33+
UnliftedFFITypes, UnboxedTuples,
34+
GeneralizedNewtypeDeriving, TypeFamilies,
35+
DeriveDataTypeable
3036

3137
C-sources: cbits/gmp-extras.cmm
32-
Include-dirs: cbits
38+
Include-dirs: cbits, include
3339

3440
if os(windows)
3541
Extra-libraries: gmp-10
@@ -52,6 +58,7 @@ Test-suite bitset-tests
5258
Hs-source-dirs: tests
5359
Ghc-options: -Wall -O2 -fno-warn-orphans
5460
Default-language: Haskell2010
61+
Other-extensions: CPP
5562

5663
Type: exitcode-stdio-1.0
5764
Main-is: Tests.hs
@@ -66,9 +73,10 @@ Benchmark bitset-benchmarks
6673
Hs-source-dirs: src benchmarks
6774
Ghc-options: -Wall -fno-warn-orphans -O2 -optc-O3 -optc-msse4.1
6875
Default-language: Haskell2010
76+
Other-extensions: CPP, ExistentialQuantification
6977

7078
C-sources: cbits/gmp-extras.cmm
71-
Include-dirs: cbits
79+
Include-dirs: cbits, include
7280
Extra-libraries: gmp
7381

7482
Type: exitcode-stdio-1.0

include/bitset.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Common macros for bitset
3+
*/
4+
5+
#ifndef HASKELL_BITSET_H
6+
#define HASKELL_BITSET_H
7+
8+
/*
9+
* We use cabal-generated MIN_VERSION_base to adapt to changes of base.
10+
* Nevertheless, as a convenience, we also allow compiling without cabal by
11+
* defining an approximate MIN_VERSION_base if needed. The alternative version
12+
* guesses the version of base using the version of GHC. This is usually
13+
* sufficiently accurate. However, it completely ignores minor version numbers,
14+
* and it makes the assumption that a pre-release version of GHC will ship with
15+
* base libraries with the same version numbers as the final release. This
16+
* assumption is violated in certain stages of GHC development, but in practice
17+
* this should very rarely matter, and will not affect any released version.
18+
*/
19+
#ifndef MIN_VERSION_base
20+
#if __GLASGOW_HASKELL__ >= 711
21+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major2) == 4)&&((major2)<=9)))
22+
#elif __GLASGOW_HASKELL__ >= 709
23+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major1) == 4)&&((major2)<=8)))
24+
#elif __GLASGOW_HASKELL__ >= 707
25+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major1) == 4)&&((major2)<=7)))
26+
#elif __GLASGOW_HASKELL__ >= 705
27+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major1) == 4)&&((major2)<=6)))
28+
#elif __GLASGOW_HASKELL__ >= 703
29+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major1) == 4)&&((major2)<=5)))
30+
#elif __GLASGOW_HASKELL__ >= 701
31+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major1) == 4)&&((major2)<=4)))
32+
#elif __GLASGOW_HASKELL__ >= 700
33+
#define MIN_VERSION_base(major1,major2,minor) (((major1)<4)||(((major1) == 4)&&((major2)<=3)))
34+
#else
35+
#define MIN_VERSION_base(major1,major2,minor) (0)
36+
#endif
37+
#endif
38+
39+
#endif

src/Data/BitSet/Dynamic.hs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
{-# LANGUAGE MagicHash #-}
33
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
44

5+
#include <bitset.h>
6+
57
-----------------------------------------------------------------------------
68
-- |
79
-- Module : Data.BitSet.Dynamic
@@ -122,11 +124,10 @@ instance Bits FasterInteger where
122124
isSigned = isSigned . unFI
123125
{-# INLINE isSigned #-}
124126

125-
bitSize = bitSize . unFI
126-
{-# INLINE bitSize #-}
127+
bitSize _ = error "bitSize: FasterInteger does not support bitSize."
127128

128-
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
129-
bitSizeMaybe = bitSizeMaybe . unFI
129+
#if MIN_VERSION_base(4,7,0)
130+
bitSizeMaybe _ = Nothing
130131
{-# INLINE bitSizeMaybe #-}
131132
#endif
132133

src/Data/BitSet/Generic.hs

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
{-# LANGUAGE DeriveDataTypeable #-}
3232
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3333

34+
#include <bitset.h>
35+
3436
module Data.BitSet.Generic
3537
(
3638
-- * Bit set type
@@ -79,6 +81,9 @@ import Control.Applicative ((<$>))
7981
import Control.DeepSeq (NFData(..))
8082
import Data.Bits (Bits, (.|.), (.&.), complement, bit,
8183
testBit, setBit, clearBit, popCount)
84+
#if MIN_VERSION_base(4,7,0)
85+
import Data.Bits (bitSizeMaybe, isSigned, unsafeShiftR, zeroBits)
86+
#endif
8287
import Data.Data (Typeable)
8388
import Data.Monoid (Monoid(..))
8489
import Foreign (Storable)
@@ -94,29 +99,35 @@ import qualified Data.List as List
9499
newtype BitSet c a = BitSet { getBits :: c }
95100
deriving (Eq, NFData, Storable, Ord, Typeable)
96101

97-
instance (Enum a, Read a, Bits c, Num c) => Read (BitSet c a) where
102+
instance (Enum a, Read a, Bits c) => Read (BitSet c a) where
98103
readPrec = parens . prec 10 $ do
99104
Ident "fromList" <- lexP
100105
fromList <$> readPrec
101106

102-
instance (Enum a, Show a, Bits c, Num c) => Show (BitSet c a) where
107+
instance (Enum a, Show a, Bits c) => Show (BitSet c a) where
103108
showsPrec p bs = showParen (p > 10) $
104109
showString "fromList " . shows (toList bs)
105110

106-
instance (Enum a, Bits c, Num c) => Monoid (BitSet c a) where
111+
instance Bits c => Monoid (BitSet c a) where
107112
mempty = empty
108113
mappend = union
109114

110115
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 707)
111-
instance (Enum a, Bits c, Num c) => IsList (BitSet c a) where
116+
instance (Enum a, Bits c) => IsList (BitSet c a) where
112117
type Item (BitSet c a) = a
113118
fromList = fromList
114119
toList = toList
115120
#endif
116121

122+
#if !MIN_VERSION_base(4,7,0)
123+
zeroBits :: Bits c => c
124+
zeroBits = bit 0 `clearBit` 0
125+
{-# INLINE zeroBits #-}
126+
#endif
127+
117128
-- | /O(1)/. Is the bit set empty?
118-
null :: (Eq c, Num c) => BitSet c a -> Bool
119-
null = (== 0) . getBits
129+
null :: Bits c => BitSet c a -> Bool
130+
null = (== zeroBits) . getBits
120131
{-# INLINE null #-}
121132

122133
-- | /O(1)/. The number of elements in the bit set.
@@ -136,22 +147,22 @@ notMember x = not . member x
136147

137148
-- | /O(max(n, m))/. Is this a subset? (@s1 `isSubsetOf` s2@) tells whether
138149
-- @s1@ is a subset of @s2@.
139-
isSubsetOf :: (Bits c, Eq c) => BitSet c a -> BitSet c a -> Bool
150+
isSubsetOf :: Bits c => BitSet c a -> BitSet c a -> Bool
140151
isSubsetOf (BitSet bits1) (BitSet bits2) = bits2 .|. bits1 == bits2
141152
{-# INLINE isSubsetOf #-}
142153

143154
-- | /O(max(n, m)/. Is this a proper subset? (ie. a subset but not equal).
144-
isProperSubsetOf :: (Bits c, Eq c) => BitSet c a -> BitSet c a -> Bool
155+
isProperSubsetOf :: Bits c => BitSet c a -> BitSet c a -> Bool
145156
isProperSubsetOf bs1 bs2 = bs1 `isSubsetOf` bs2 && bs1 /= bs2
146157
{-# INLINE isProperSubsetOf #-}
147158

148159
-- | The empty bit set.
149-
empty :: (Enum a, Bits c, Num c) => BitSet c a
150-
empty = BitSet 0
160+
empty :: Bits c => BitSet c a
161+
empty = BitSet zeroBits
151162
{-# INLINE empty #-}
152163

153164
-- | O(1). Create a singleton set.
154-
singleton :: (Enum a, Bits c, Num c) => a -> BitSet c a
165+
singleton :: (Enum a, Bits c) => a -> BitSet c a
155166
singleton = BitSet . bit . fromEnum
156167
{-# INLINE singleton #-}
157168

@@ -186,7 +197,7 @@ intersection (BitSet bits1) (BitSet bits2) = BitSet $ bits1 .&. bits2
186197

187198
-- | /O(d * n)/ Transform this bit set by applying a function to every
188199
-- value. Resulting bit set may be smaller then the original.
189-
map :: (Enum a, Enum b, Bits c, Num c) => (a -> b) -> BitSet c a -> BitSet c b
200+
map :: (Enum a, Enum b, Bits c) => (a -> b) -> BitSet c a -> BitSet c b
190201
map f = foldl' (\bs -> (`insert` bs) . f) empty
191202
{-# INLINE map #-}
192203

@@ -195,37 +206,63 @@ map f = foldl' (\bs -> (`insert` bs) . f) empty
195206
-- operator is evaluated before before using the result in the next
196207
-- application. This function is strict in the starting value.
197208
foldl' :: (Enum a, Bits c) => (b -> a -> b) -> b -> BitSet c a -> b
198-
foldl' f acc0 (BitSet bits) = go acc0 (popCount bits) 0 where
199-
go !acc 0 _b = acc
200-
go !acc !n b = if bits `testBit` b
201-
then go (f acc $ toEnum b) (pred n) (succ b)
202-
else go acc n (succ b)
209+
#if MIN_VERSION_base(4,7,0)
210+
-- If the bit set is represented by an unsigned type
211+
-- then we can shift the bits off one by one until we're
212+
-- left with all zeros. If the type is fairly narrow, then
213+
-- this is likely to be cheap. In particular, in this case
214+
-- we don't need to calculate the `popCount` and all shifts
215+
-- are by fixed amounts.
216+
foldl' f acc0 (BitSet bits0)
217+
| not (isSigned bits0) && maybe False (<= 128) (bitSizeMaybe bits0) =
218+
go acc0 bits0 0
219+
where
220+
go !acc !bits !b
221+
| bits == zeroBits = acc
222+
| bits `testBit` 0 = go (f acc $ toEnum b) (bits `unsafeShiftR` 1) (b + 1)
223+
| otherwise = go acc (bits `unsafeShiftR` 1) (b + 1)
224+
#endif
225+
foldl' f acc0 (BitSet bits) = go acc0 (popCount bits) 0
226+
where
227+
go !acc 0 !_b = acc
228+
go !acc n !b = if bits `testBit` b
229+
then go (f acc $ toEnum b) (n - 1) (b + 1)
230+
else go acc n (b + 1)
203231
{-# INLINE foldl' #-}
204232

205233
-- | /O(d * n)/ Reduce this bit set by applying a binary function to
206234
-- all elements, using the given starting value.
207235
foldr :: (Enum a, Bits c) => (a -> b -> b) -> b -> BitSet c a -> b
236+
#if MIN_VERSION_base(4,7,0)
237+
foldr f acc0 (BitSet bits0)
238+
| not (isSigned bits0) && maybe False (<= 128) (bitSizeMaybe bits0) = go bits0 0
239+
where
240+
go !bits !b
241+
| bits == zeroBits = acc0
242+
| bits `testBit` 0 = toEnum b `f` go (bits `unsafeShiftR` 1) (b + 1)
243+
| otherwise = go (bits `unsafeShiftR` 1) (b + 1)
244+
#endif
208245
foldr f acc0 (BitSet bits) = go (popCount bits) 0 where
209246
go 0 _b = acc0
210247
go !n b = if bits `testBit` b
211-
then toEnum b `f` go (pred n) (succ b)
212-
else go n (succ b)
248+
then toEnum b `f` go (n - 1) (b + 1)
249+
else go n (b + 1)
213250
{-# INLINE foldr #-}
214251

215252
-- | /O(d * n)/ Filter this bit set by retaining only elements satisfying
216253
-- predicate.
217-
filter :: (Enum a, Bits c, Num c) => (a -> Bool) -> BitSet c a -> BitSet c a
254+
filter :: (Enum a, Bits c) => (a -> Bool) -> BitSet c a -> BitSet c a
218255
filter f = foldl' (\bs x -> if f x then x `insert` bs else bs) empty
219256
{-# INLINE filter #-}
220257

221258
-- | /O(d * n)/. Convert this bit set set to a list of elements.
222-
toList :: (Enum a, Bits c, Num c) => BitSet c a -> [a]
259+
toList :: (Enum a, Bits c) => BitSet c a -> [a]
223260
toList bs = build (\k z -> foldr k z bs)
224261
{-# INLINE [0] toList #-}
225262

226263
-- | /O(d * n)/. Make a bit set from a list of elements.
227-
fromList :: (Enum a, Bits c, Num c) => [a] -> BitSet c a
228-
fromList = BitSet . List.foldl' (\i x -> i `setBit` fromEnum x) 0
264+
fromList :: (Enum a, Bits c) => [a] -> BitSet c a
265+
fromList = List.foldl' (\i x -> insert x i) empty
229266
{-# INLINE [0] fromList #-}
230267
{-# RULES
231268
"fromList/toList" forall bs. fromList (toList bs) = bs

0 commit comments

Comments
 (0)