packages feed

bitwise-enum (empty) → 0.1.0.0

raw patch · 9 files changed

+1694/−0 lines, 9 filesdep +QuickCheckdep +aesondep +arraysetup-changed

Dependencies added: QuickCheck, aeson, array, base, bitwise-enum, deepseq, gauge, mono-traversable, test-framework, test-framework-quickcheck2, vector, wide-word

Files

+ Data/Enum/Memo.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE ExplicitForAll      #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE UnicodeSyntax       #-}
+{-# LANGUAGE TypeApplications    #-}
+
+-- | Immutable lazy tables of functions over bounded enumerations.
+-- Function calls are stored as thunks and not evaluated until accessed.
+--
+-- The underlying representation is an 'Data.Array.Array' rather than a search
+-- tree. This provides /O(1)/ lookup but means that the range of keys should not
+-- be very large, as in the case of an 'Int'-like type.
+module Data.Enum.Memo
+  ( -- * Memoization
+    memoize
+    -- * Higher-order
+  , memoize2
+  , memoize3
+  , memoize4
+  , memoize5
+  ) where
+
+import Prelude hiding (lookup)
+
+import Data.Array ((!), array)
+
+-- | Memoize a function with a single argument.
+memoize :: ∀ k v. (Bounded k, Enum k) => (k -> v) -> k -> v
+memoize f = case array bounds vals of
+    memo -> \k -> memo ! fromEnum k
+  where
+    bounds = (fromEnum @k minBound, fromEnum @k maxBound)
+    vals   = [(fromEnum k, f k) | k <- [minBound..maxBound]]
+
+-- | Memoize a function with two arguments.
+memoize2 :: ∀ k1 k2 v.
+            ( Bounded k1, Enum k1
+            , Bounded k2, Enum k2
+            )
+         => (k1 -> k2 -> v) -> k1 -> k2 -> v
+memoize2 f = case array bounds vals of
+    memo -> \k1 k2 -> memo !
+            ( fromEnum k1
+            , fromEnum k2
+            )
+  where
+    bounds = ( ( fromEnum @k1 minBound
+               , fromEnum @k2 minBound
+               )
+             , ( fromEnum @k1 maxBound
+               , fromEnum @k2 maxBound
+               )
+             )
+    vals   = [( ( fromEnum k1
+                , fromEnum k2
+                )
+              , f k1 k2
+              ) | k1 <- [minBound..maxBound]
+                , k2 <- [minBound..maxBound]]
+
+-- | Memoize a function with three arguments.
+memoize3 :: ∀ k1 k2 k3 v.
+            ( Bounded k1, Enum k1
+            , Bounded k2, Enum k2
+            , Bounded k3, Enum k3
+            )
+         => (k1 -> k2 -> k3 -> v) -> k1 -> k2 -> k3 -> v
+memoize3 f = case array bounds vals of
+    memo -> \k1 k2 k3 -> memo !
+            ( fromEnum k1
+            , fromEnum k2
+            , fromEnum k3
+            )
+  where
+    bounds = ( ( fromEnum @k1 minBound
+               , fromEnum @k2 minBound
+               , fromEnum @k3 minBound
+               )
+             , ( fromEnum @k1 maxBound
+               , fromEnum @k2 maxBound
+               , fromEnum @k3 maxBound
+               )
+             )
+    vals   = [( ( fromEnum k1
+                , fromEnum k2
+                , fromEnum k3
+                )
+              , f k1 k2 k3
+              ) | k1 <- [minBound..maxBound]
+                , k2 <- [minBound..maxBound]
+                , k3 <- [minBound..maxBound]]
+
+-- | Memoize a function with four arguments.
+memoize4 :: ∀ k1 k2 k3 k4 v.
+            ( Bounded k1, Enum k1
+            , Bounded k2, Enum k2
+            , Bounded k3, Enum k3
+            , Bounded k4, Enum k4
+            )
+         => (k1 -> k2 -> k3 -> k4 -> v) -> k1 -> k2 -> k3 -> k4 -> v
+memoize4 f = case array bounds vals of
+    memo -> \k1 k2 k3 k4 -> memo !
+            ( fromEnum k1
+            , fromEnum k2
+            , fromEnum k3
+            , fromEnum k4
+            )
+  where
+    bounds = ( ( fromEnum @k1 minBound
+               , fromEnum @k2 minBound
+               , fromEnum @k3 minBound
+               , fromEnum @k4 minBound
+               )
+             , ( fromEnum @k1 maxBound
+               , fromEnum @k2 maxBound
+               , fromEnum @k3 maxBound
+               , fromEnum @k4 maxBound
+               )
+             )
+    vals   = [( ( fromEnum k1
+                , fromEnum k2
+                , fromEnum k3
+                , fromEnum k4
+                )
+              , f k1 k2 k3 k4
+              ) | k1 <- [minBound..maxBound]
+                , k2 <- [minBound..maxBound]
+                , k3 <- [minBound..maxBound]
+                , k4 <- [minBound..maxBound]]
+
+-- | Memoize a function with five arguments.
+memoize5 :: ∀ k1 k2 k3 k4 k5 v.
+            ( Bounded k1, Enum k1
+            , Bounded k2, Enum k2
+            , Bounded k3, Enum k3
+            , Bounded k4, Enum k4
+            , Bounded k5, Enum k5
+            )
+         => (k1 -> k2 -> k3 -> k4 -> k5 -> v) -> k1 -> k2 -> k3 -> k4 -> k5 -> v
+memoize5 f = case array bounds vals of
+    memo -> \k1 k2 k3 k4 k5 -> memo !
+            ( fromEnum k1
+            , fromEnum k2
+            , fromEnum k3
+            , fromEnum k4
+            , fromEnum k5
+            )
+  where
+    bounds = ( ( fromEnum @k1 minBound
+               , fromEnum @k2 minBound
+               , fromEnum @k3 minBound
+               , fromEnum @k4 minBound
+               , fromEnum @k5 minBound
+               )
+             , ( fromEnum @k1 maxBound
+               , fromEnum @k2 maxBound
+               , fromEnum @k3 maxBound
+               , fromEnum @k4 maxBound
+               , fromEnum @k5 maxBound
+               )
+             )
+    vals   = [( ( fromEnum k1
+                , fromEnum k2
+                , fromEnum k3
+                , fromEnum k4
+                , fromEnum k5
+                )
+              , f k1 k2 k3 k4 k5
+              ) | k1 <- [minBound..maxBound]
+                , k2 <- [minBound..maxBound]
+                , k3 <- [minBound..maxBound]
+                , k4 <- [minBound..maxBound]
+                , k5 <- [minBound..maxBound]]
+ Data/Enum/Set.hs view
@@ -0,0 +1,384 @@+{-# LANGUAGE ExplicitForAll   #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies     #-}
+{-# LANGUAGE UnicodeSyntax    #-}
+
+-- | Efficient sets over bounded enumerations, using bitwise operations based on
+-- [containers](https://hackage.haskell.org/package/containers-0.6.0.1/docs/src/Data.IntSet.Internal.html)
+-- and
+-- [EdisonCore](https://hackage.haskell.org/package/EdisonCore-1.3.2.1/docs/src/Data-Edison-Coll-EnumSet.html).
+-- In many cases, @EnumSet@s may be optimised away entirely by constant folding
+-- at compile-time. For example, in the following code:
+--
+-- @
+-- import Data.Enum.Set as E
+--
+-- data Foo = A | B | C | D | E | F | G | H deriving (Bounded, Enum, Eq, Ord)
+--
+-- instance E.AsEnumSet Foo
+--
+-- addFoos :: E.EnumSet Foo -> E.EnumSet Foo
+-- addFoos = E.delete A . E.insert B
+--
+-- bar :: E.EnumSet Foo
+-- bar = addFoos $ E.fromFoldable [A, C, E]
+--
+-- barHasA :: Bool
+-- barHasA = E.member A bar
+-- @
+--
+-- With @-O@ or @-O2@, @bar@ will compile to @GHC.Types.W\# 22\#\#@ and
+-- @barHasA@ will compile to @GHC.Types.False@.
+--
+-- By default, 'Word's are used as the representation. Other representations may
+-- be chosen in the class instance:
+--
+-- @
+-- {-\# LANGUAGE TypeFamilies \#-}
+--
+-- import Data.Enum.Set as E
+-- import Data.Word (Word64)
+--
+-- data Foo = A | B | C | D | E | F | G | H deriving (Bounded, Enum, Eq, Ord, Show)
+--
+-- instance E.AsEnumSet Foo where
+--     type EnumSetRep Foo = Word64
+--
+-- @
+--
+-- For type @EnumSet E@, @EnumSetRep E@ should be a @Word@-like type that
+-- implements 'Bits' and 'Num', and @E@ should be a type that implements 'Eq'
+-- and 'Enum' equivalently and is a bijection to 'Int'.
+-- @EnumSet E@ can only store a value of @E@ if the result of applying
+-- 'fromEnum' to the value is positive and less than the number of bits in
+-- @EnumSetRep E@. For this reason, it is preferable for @E@ to be a type that
+-- derives @Eq@ and @Enum@, and for @EnumSetRep E@ to have more bits than the
+-- number of constructors of @E@.
+--
+-- If the highest @fromEnum@ value of @E@ is 29, @EnumSetRep E@ should be
+-- 'Word', because it always has at least 30 bits. This is the
+-- default implementation.
+-- Otherwise, options include 'Data.Word.Word32', 'Data.Word.Word64', and the
+-- [wide-word](https://hackage.haskell.org/package/wide-word-0.1.0.8/) package's
+-- [Data.WideWord.Word128](https://hackage.haskell.org/package/wide-word-0.1.0.8/docs/Data-WideWord-Word128.html).
+-- Foreign types may also be used.
+--
+-- Note: complexity calculations assume that @EnumSetRep E@ implements 'Bits'
+-- with constant-time functions, as is the case with @Word@ etc. Otherwise, the
+-- complexity of those operations should be added to the complexity of @EnumSet@
+-- functions.
+module Data.Enum.Set
+  ( AsEnumSet(..)
+  -- * Set type
+  , EnumSet
+    -- * Construction
+  , empty
+  , singleton
+  , fromFoldable
+
+  -- * Insertion
+  , insert
+
+  -- * Deletion
+  , delete
+
+  -- * Query
+  , member
+  , notMember
+  , null
+  , size
+  , isSubsetOf
+
+  -- * Combine
+  , union
+  , difference
+  , (\\)
+  , symmetricDifference
+  , intersection
+
+  -- * Filter
+  , filter
+  , partition
+
+  -- * Map
+  , map
+
+  -- * Folds
+  , foldl, foldl', foldr, foldr'
+  , foldl1, foldl1', foldr1, foldr1'
+
+  -- ** Special folds
+  , foldMap
+  , traverse
+  , any
+  , all
+
+   -- * Min/Max
+  , minimum
+  , maximum
+  , deleteMin
+  , deleteMax
+  , minView
+  , maxView
+
+  -- * Conversion
+  , toList
+  , fromRaw
+  ) where
+
+import Prelude hiding (all, any, filter, foldl, foldl1, foldMap, foldr, foldr1, map, maximum, minimum, null, traverse)
+
+import Data.Bits
+import Data.Monoid (Monoid(..))
+
+import qualified Data.Enum.Set.Base as E
+
+class (Enum a, FiniteBits (EnumSetRep a), Num (EnumSetRep a)) => AsEnumSet a where
+    type EnumSetRep a
+    type EnumSetRep a = Word
+
+type EnumSet a = E.EnumSet (EnumSetRep a) a
+
+{--------------------------------------------------------------------
+  Construction
+--------------------------------------------------------------------}
+
+-- | /O(1)/. The empty set.
+empty :: ∀ a. AsEnumSet a => EnumSet a
+empty = E.empty
+{-# INLINE empty #-}
+
+-- | /O(1)/. A set of one element.
+singleton :: ∀ a. AsEnumSet a => a -> EnumSet a
+singleton = E.singleton
+{-# INLINE singleton #-}
+
+-- | /O(n)/. Create a set from a finite foldable data structure.
+fromFoldable :: ∀ f a. (Foldable f, AsEnumSet a) => f a -> EnumSet a
+fromFoldable = E.fromFoldable
+{-# INLINE fromFoldable #-}
+
+{--------------------------------------------------------------------
+  Insertion
+--------------------------------------------------------------------}
+
+-- | /O(1)/. Add a value to the set.
+insert :: ∀ a. AsEnumSet a => a -> EnumSet a -> EnumSet a
+insert = E.insert
+{-# INLINE insert #-}
+
+{--------------------------------------------------------------------
+  Deletion
+--------------------------------------------------------------------}
+
+-- | /O(1)/. Delete a value in the set.
+delete :: ∀ a. AsEnumSet a => a -> EnumSet a -> EnumSet a
+delete = E.delete
+{-# INLINE delete #-}
+
+{--------------------------------------------------------------------
+  Query
+--------------------------------------------------------------------}
+
+-- | /O(1)/. Is the value a member of the set?
+member :: ∀ a. AsEnumSet a => a -> EnumSet a -> Bool
+member = E.member
+{-# INLINE member #-}
+
+-- | /O(1)/. Is the value not in the set?
+notMember :: ∀ a. AsEnumSet a => a -> EnumSet a -> Bool
+notMember = E.notMember
+{-# INLINE notMember #-}
+
+-- | /O(1)/. Is this the empty set?
+null :: ∀ a. AsEnumSet a => EnumSet a -> Bool
+null = E.null
+{-# INLINE null #-}
+
+-- | /O(1)/. The number of elements in the set.
+size :: ∀ a. AsEnumSet a => EnumSet a -> Int
+size = E.size
+{-# INLINE size #-}
+
+-- | /O(1)/. Is this a subset?
+-- @(s1 `isSubsetOf` s2)@ tells whether @s1@ is a subset of @s2@.
+isSubsetOf :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a -> Bool
+isSubsetOf = E.isSubsetOf
+{-# INLINE isSubsetOf #-}
+
+{--------------------------------------------------------------------
+  Combine
+--------------------------------------------------------------------}
+
+-- | /O(1)/. The union of two sets.
+union :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
+union = E.union
+{-# INLINE union #-}
+
+-- | /O(1)/. Difference between two sets.
+difference :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
+difference = E.difference
+{-# INLINE difference #-}
+
+-- | /O(1)/. See 'difference'.
+(\\) :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
+(\\) = E.difference
+infixl 9 \\
+{-# INLINE (\\) #-}
+
+-- | /O(1)/. Elements which are in either set, but not both.
+symmetricDifference :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
+symmetricDifference = E.symmetricDifference
+{-# INLINE symmetricDifference #-}
+
+-- | /O(1)/. The intersection of two sets.
+intersection :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a -> EnumSet a
+intersection = E.intersection
+{-# INLINE intersection #-}
+
+{--------------------------------------------------------------------
+  Filter
+--------------------------------------------------------------------}
+
+-- | /O(n)/. Filter all elements that satisfy some predicate.
+filter :: ∀ a. AsEnumSet a => (a -> Bool) -> EnumSet a -> EnumSet a
+filter = E.filter
+{-# INLINE filter #-}
+
+-- | /O(n)/. Partition the set according to some predicate.
+-- The first set contains all elements that satisfy the predicate,
+-- the second all elements that fail the predicate.
+partition :: ∀ a. AsEnumSet a
+          => (a -> Bool) -> EnumSet a -> (EnumSet a, EnumSet a)
+partition = E.partition
+{-# INLINE partition #-}
+
+{--------------------------------------------------------------------
+  Map
+--------------------------------------------------------------------}
+
+-- | /O(n)/.
+-- @'map' f s@ is the set obtained by applying @f@ to each element of @s@.
+--
+-- It's worth noting that the size of the result may be smaller if,
+-- for some @(x,y)@, @x \/= y && f x == f y@
+map :: ∀ a b. (AsEnumSet a, AsEnumSet b) => (a -> b) -> EnumSet a -> EnumSet b
+map = E.map'
+
+{--------------------------------------------------------------------
+  Folds
+--------------------------------------------------------------------}
+
+-- | /O(n)/. Left fold.
+foldl :: ∀ a b. AsEnumSet a => (b -> a -> b) -> b -> EnumSet a -> b
+foldl = E.foldl
+{-# INLINE foldl #-}
+
+-- | /O(n)/. Left fold with strict accumulator.
+foldl' :: ∀ a b. AsEnumSet a => (b -> a -> b) -> b -> EnumSet a -> b
+foldl' = E.foldl'
+{-# INLINE foldl' #-}
+
+-- | /O(n)/. Right fold.
+foldr :: ∀ a b. AsEnumSet a => (a -> b -> b) -> b -> EnumSet a -> b
+foldr = E.foldr
+{-# INLINE foldr #-}
+
+-- | /O(n)/. Right fold with strict accumulator.
+foldr' :: ∀ a b. AsEnumSet a => (a -> b -> b) -> b -> EnumSet a -> b
+foldr' = E.foldr'
+{-# INLINE foldr' #-}
+
+-- | /O(n)/. Left fold on non-empty sets.
+foldl1 :: ∀ a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
+foldl1 = E.foldl1
+{-# INLINE foldl1 #-}
+
+-- | /O(n)/. Left fold on non-empty sets with strict accumulator.
+foldl1' :: ∀ a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
+foldl1' = E.foldl1'
+{-# INLINE foldl1' #-}
+
+-- | /O(n)/. Right fold on non-empty sets.
+foldr1 :: ∀ a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
+foldr1 = E.foldr1
+{-# INLINE foldr1 #-}
+
+-- | /O(n)/. Right fold on non-empty sets with strict accumulator.
+foldr1' :: ∀ a. AsEnumSet a => (a -> a -> a) -> EnumSet a -> a
+foldr1' = E.foldr1'
+{-# INLINE foldr1'  #-}
+
+-- | /O(n)/. Map each element of the structure to a monoid, and combine the
+-- results.
+foldMap :: ∀ m a. (Monoid m, AsEnumSet a) => (a -> m) -> EnumSet a -> m
+foldMap = E.foldMap
+{-# INLINE foldMap #-}
+
+traverse :: ∀ f a. (Applicative f, AsEnumSet a)
+         => (a -> f a) -> EnumSet a -> f (EnumSet a)
+traverse = E.traverse
+{-# INLINE traverse #-}
+
+-- | /O(n)/. Check if all elements satisfy some predicate.
+all :: ∀ a. AsEnumSet a => (a -> Bool) -> EnumSet a -> Bool
+all = E.all
+{-# INLINE all #-}
+
+-- | /O(n)/. Check if any element satisfies some predicate.
+any :: ∀ a. AsEnumSet a => (a -> Bool) -> EnumSet a -> Bool
+any = E.any
+{-# INLINE any #-}
+
+{--------------------------------------------------------------------
+  Min/Max
+--------------------------------------------------------------------}
+
+-- | /O(1)/. The minimal element of a non-empty set.
+minimum :: ∀ a. AsEnumSet a => EnumSet a -> a
+minimum = E.minimum
+{-# INLINE minimum #-}
+
+-- | /O(1)/. The maximal element of a non-empty set.
+maximum :: ∀ a. AsEnumSet a => EnumSet a -> a
+maximum = E.maximum
+{-# INLINE maximum #-}
+
+-- | /O(1)/. Delete the minimal element.
+deleteMin :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a
+deleteMin = E.deleteMin
+{-# INLINE deleteMin #-}
+
+-- | /O(1)/. Delete the maximal element.
+deleteMax :: ∀ a. AsEnumSet a => EnumSet a -> EnumSet a
+deleteMax = E.deleteMax
+{-# INLINE deleteMax #-}
+
+-- | /O(1)/. Retrieves the minimal element of the set,
+-- and the set stripped of that element,
+-- or Nothing if passed an empty set.
+minView :: ∀ a. AsEnumSet a => EnumSet a -> Maybe (a, EnumSet a)
+minView = E.minView
+{-# INLINE minView #-}
+
+-- | /O(1)/. Retrieves the maximal element of the set,
+-- and the set stripped of that element,
+-- or Nothing if passed an empty set.
+maxView :: ∀ a. AsEnumSet a => EnumSet a -> Maybe (a, EnumSet a)
+maxView = E.maxView
+{-# INLINE maxView #-}
+
+{--------------------------------------------------------------------
+  Conversion
+--------------------------------------------------------------------}
+
+-- | /O(n)/. Convert the set to a list of values.
+toList :: ∀ a. AsEnumSet a => EnumSet a -> [a]
+toList = E.toList
+{-# INLINE toList #-}
+
+-- | /O(1)/. Convert a representation into an @EnumSet@.
+-- Intended for use with foreign types.
+fromRaw :: ∀ a. AsEnumSet a => EnumSetRep a -> EnumSet a
+fromRaw = E.fromRaw
+{-# INLINE fromRaw #-}
+ Data/Enum/Set/Base.hs view
@@ -0,0 +1,691 @@+{-# LANGUAGE BangPatterns               #-}
+{-# LANGUAGE BlockArguments             #-}
+{-# LANGUAGE DataKinds                  #-}
+{-# LANGUAGE DeriveDataTypeable         #-}
+{-# LANGUAGE ExplicitForAll             #-}
+{-# LANGUAGE MultiParamTypeClasses      #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE PolyKinds                  #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE TypeFamilies               #-}
+{-# LANGUAGE UnboxedTuples              #-}
+{-# LANGUAGE UnicodeSyntax              #-}
+
+-- | Efficient sets over bounded enumerations, using bitwise operations based on
+-- [containers](https://hackage.haskell.org/package/containers-0.6.0.1/docs/src/Data.IntSet.Internal.html)
+-- and
+-- [EdisonCore](https://hackage.haskell.org/package/EdisonCore-1.3.2.1/docs/src/Data-Edison-Coll-EnumSet.html).
+-- In many cases, @EnumSet@s may be optimised away entirely by constant folding
+-- at compile-time. For example, in the following code:
+--
+-- @
+--
+-- import Data.Enum.Set.Base as E
+--
+-- data Foo = A | B | C | D | E | F | G | H deriving (Bounded, Enum, Eq, Ord, Show)
+--
+-- addFoos :: E.EnumSet Word Foo -> E.EnumSet Word Foo
+-- addFoos = E.delete A . E.insert B
+--
+-- bar :: E.EnumSet Word Foo
+-- bar = addFoos $ E.fromFoldable [A, C, E]
+--
+-- barHasB :: Bool
+-- barHasB = E.member A bar
+--
+-- @
+--
+-- With @-O@ or @-O2@, @bar@ will compile to @GHC.Types.W\# 22\#\#@ and
+-- @barHasA@ will compile to @GHC.Types.False@.
+--
+-- For type @EnumSet W E@, @W@ should be a 'Word'-like type that implements
+-- 'Bits' and 'Num', and @E@ should be a type that implements 'Eq' and 'Enum'
+-- equivalently and is a bijection to 'Int'.
+-- @EnumSet W E@ can only store a value of @E@ if the result of applying
+-- 'fromEnum' to the value is positive and less than the number of bits in @W@.
+-- For this reason, it is preferable for @E@ to be a type that derives 'Eq' and
+-- 'Enum', and for @W@ to have more bits than the number of constructors of @E@.
+--
+-- For type @EnumSet W E@, if the highest @fromEnum@ value of @E@ is 29,
+-- @W@ should be 'Data.Word.Word', because it always has at least 30 bits.
+-- Otherwise, options include 'Data.Word.Word32', 'Data.Word.Word64', and the
+-- [wide-word](https://hackage.haskell.org/package/wide-word-0.1.0.8/) package's
+-- [Data.WideWord.Word128](https://hackage.haskell.org/package/wide-word-0.1.0.8/docs/Data-WideWord-Word128.html).
+-- Foreign types may also be used.
+--
+-- "Data.Enum.Set" provides an alternate type alias that moves the underlying
+-- representation to an associated type token, so that e.g.
+-- @EnumSet Word64 MyEnum@ is replaced by @EnumSet MyEnum@, and reexports this
+-- module with adjusted type signatures.
+--
+-- Note: complexity calculations assume that @W@ implements 'Bits' with
+-- constant-time functions, as is the case with 'Data.Word.Word' etc. If this
+-- is not the case, the complexity of those operations should be added to the
+-- complexity of 'EnumSet' functions.
+module Data.Enum.Set.Base
+  ( -- * Set type
+    EnumSet
+
+    -- * Construction
+  , empty
+  , singleton
+  , fromFoldable
+
+  -- * Insertion
+  , insert
+
+  -- * Deletion
+  , delete
+
+  -- * Query
+  , member
+  , notMember
+  , null
+  , size
+  , isSubsetOf
+
+  -- * Combine
+  , union
+  , difference
+  , (\\)
+  , symmetricDifference
+  , intersection
+
+  -- * Filter
+  , filter
+  , partition
+
+  -- * Map
+  , map
+  , map'
+
+  -- * Folds
+  , foldl, foldl', foldr, foldr'
+  , foldl1, foldl1', foldr1, foldr1'
+
+  -- ** Special folds
+  , foldMap
+  , traverse
+  , any
+  , all
+
+   -- * Min/Max
+  , minimum
+  , maximum
+  , deleteMin
+  , deleteMax
+  , minView
+  , maxView
+
+  -- * Conversion
+  , toList
+  , fromRaw
+  ) where
+
+import qualified GHC.Exts
+import qualified Data.Foldable as F
+
+import Prelude hiding (all, any, filter, foldl, foldl1, foldMap, foldr, foldr1, map, maximum, minimum, null, traverse)
+
+import Control.Applicative (liftA2)
+import Control.DeepSeq (NFData)
+import Control.Monad
+import Data.Aeson (ToJSON(..))
+import Data.Bits
+import Data.Data (Data)
+import Data.Monoid (Monoid(..))
+import Data.Vector.Unboxed (Vector, MVector, Unbox)
+import Foreign.Storable (Storable)
+import GHC.Exts (IsList(Item), build)
+import Text.Read
+
+import qualified Data.Vector.Generic as G
+import qualified Data.Vector.Generic.Mutable as M
+import qualified Data.Vector.Primitive as P
+
+import qualified Data.Containers
+import           Data.Containers (SetContainer, IsSet)
+import qualified Data.MonoTraversable
+import           Data.MonoTraversable (Element, GrowingAppend, MonoFoldable, MonoFunctor, MonoPointed, MonoTraversable)
+
+{--------------------------------------------------------------------
+  Set type
+--------------------------------------------------------------------}
+
+-- | A set of values @a@ with representation @word@,
+-- implemented as bitwise operations.
+newtype EnumSet word a = EnumSet word
+    deriving (Eq, Ord, Data, Storable, NFData, P.Prim, Unbox)
+
+newtype instance MVector s (EnumSet word a) = MV_EnumSet (P.MVector s (EnumSet word a))
+newtype instance Vector    (EnumSet word a) = V_EnumSet (P.Vector (EnumSet word a))
+
+instance P.Prim word => M.MVector MVector (EnumSet word a) where
+    basicLength (MV_EnumSet v) = M.basicLength v
+    {-# INLINE basicLength #-}
+    basicUnsafeSlice i n (MV_EnumSet v) = MV_EnumSet $ M.basicUnsafeSlice i n v
+    {-# INLINE basicUnsafeSlice #-}
+    basicOverlaps (MV_EnumSet v1) (MV_EnumSet v2) = M.basicOverlaps v1 v2
+    {-# INLINE basicOverlaps #-}
+    basicUnsafeNew n = MV_EnumSet `liftM` M.basicUnsafeNew n
+    {-# INLINE basicUnsafeNew #-}
+    basicInitialize (MV_EnumSet v) = M.basicInitialize v
+    {-# INLINE basicInitialize #-}
+    basicUnsafeReplicate n x = MV_EnumSet `liftM` M.basicUnsafeReplicate n x
+    {-# INLINE basicUnsafeReplicate #-}
+    basicUnsafeRead (MV_EnumSet v) i = M.basicUnsafeRead v i
+    {-# INLINE basicUnsafeRead #-}
+    basicUnsafeWrite (MV_EnumSet v) i x = M.basicUnsafeWrite v i x
+    {-# INLINE basicUnsafeWrite #-}
+    basicClear (MV_EnumSet v) = M.basicClear v
+    {-# INLINE basicClear #-}
+    basicSet (MV_EnumSet v) x = M.basicSet v x
+    {-# INLINE basicSet #-}
+    basicUnsafeCopy (MV_EnumSet v1) (MV_EnumSet v2) = M.basicUnsafeCopy v1 v2
+    {-# INLINE basicUnsafeCopy #-}
+    basicUnsafeMove (MV_EnumSet v1) (MV_EnumSet v2) = M.basicUnsafeMove v1 v2
+    {-# INLINE basicUnsafeMove #-}
+    basicUnsafeGrow (MV_EnumSet v) n = MV_EnumSet `liftM` M.basicUnsafeGrow v n
+    {-# INLINE basicUnsafeGrow #-}
+
+
+instance P.Prim word => G.Vector Vector (EnumSet word a) where
+    basicUnsafeFreeze (MV_EnumSet v) = V_EnumSet `liftM` G.basicUnsafeFreeze v
+    {-# INLINE basicUnsafeFreeze #-}
+    basicUnsafeThaw (V_EnumSet v) = MV_EnumSet `liftM` G.basicUnsafeThaw v
+    {-# INLINE basicUnsafeThaw #-}
+    basicLength (V_EnumSet v) = G.basicLength v
+    {-# INLINE basicLength #-}
+    basicUnsafeSlice i n (V_EnumSet v) = V_EnumSet $ G.basicUnsafeSlice i n v
+    {-# INLINE basicUnsafeSlice #-}
+    basicUnsafeIndexM (V_EnumSet v) i = G.basicUnsafeIndexM v i
+    {-# INLINE basicUnsafeIndexM #-}
+    basicUnsafeCopy (MV_EnumSet mv) (V_EnumSet v) = G.basicUnsafeCopy mv v
+    {-# INLINE basicUnsafeCopy #-}
+    elemseq _ = seq
+    {-# INLINE elemseq #-}
+
+instance Bits w => Semigroup (EnumSet w a) where
+    (<>) = union
+    {-# INLINE (<>) #-}
+
+instance Bits w => Monoid (EnumSet w a) where
+    mempty = empty
+    {-# INLINE mempty #-}
+
+instance (Bits w, Enum a) => MonoPointed (EnumSet w a) where
+    opoint = singleton
+    {-# INLINE opoint #-}
+
+instance (FiniteBits w, Num w, Enum a) => IsList (EnumSet w a) where
+    type Item (EnumSet w a) = a
+    fromList = fromFoldable
+    {-# INLINE fromList #-}
+    toList = toList
+    {-# INLINE toList #-}
+
+instance (FiniteBits w, Num w, Enum a, ToJSON a) => ToJSON (EnumSet w a) where
+    toJSON = toJSON . toList
+    {-# INLINE toJSON #-}
+    toEncoding = toEncoding . toList
+    {-# INLINE toEncoding #-}
+
+type instance Element (EnumSet w a) = a
+
+instance (FiniteBits w, Num w, Enum a) => MonoFunctor (EnumSet w a) where
+    omap = map
+    {-# INLINE omap #-}
+
+instance (FiniteBits w, Num w, Enum a) => MonoFoldable (EnumSet w a) where
+    ofoldMap = foldMap
+    {-# INLINE ofoldMap #-}
+    ofoldr = foldr
+    {-# INLINE ofoldr #-}
+    ofoldl' = foldl'
+    {-# INLINE ofoldl' #-}
+    ofoldr1Ex = foldr1
+    {-# INLINE ofoldr1Ex #-}
+    ofoldl1Ex' = foldl1'
+    {-# INLINE ofoldl1Ex' #-}
+    otoList = toList
+    {-# INLINE otoList #-}
+    oall = all
+    {-# INLINE oall #-}
+    oany = any
+    {-# INLINE oany #-}
+    onull = null
+    {-# INLINE onull #-}
+    olength = size
+    {-# INLINE olength #-}
+    olength64 w = fromIntegral $ size w
+    {-# INLINE olength64 #-}
+    headEx = minimum
+    {-# INLINE headEx #-}
+    lastEx = maximum
+    {-# INLINE lastEx #-}
+    oelem = member
+    {-# INLINE oelem #-}
+    onotElem x = not . member x
+    {-# INLINE onotElem #-}
+
+instance (FiniteBits w, Num w, Enum a) => GrowingAppend (EnumSet w a)
+
+instance (FiniteBits w, Num w, Enum a) => MonoTraversable (EnumSet w a) where
+    otraverse = traverse
+    {-# INLINE otraverse #-}
+
+instance (FiniteBits w, Num w, Eq a, Enum a) => SetContainer (EnumSet w a) where
+    type ContainerKey (EnumSet w a) = a
+    member = member
+    {-# INLINE member #-}
+    notMember = notMember
+    {-# INLINE notMember #-}
+    union = union
+    {-# INLINE union #-}
+    difference = difference
+    {-# INLINE difference #-}
+    intersection = intersection
+    {-# INLINE intersection #-}
+    keys = toList
+    {-# INLINE keys #-}
+
+instance (FiniteBits w, Num w, Eq a, Enum a) => IsSet (EnumSet w a) where
+    insertSet = insert
+    {-# INLINE insertSet #-}
+    deleteSet = delete
+    {-# INLINE deleteSet #-}
+    singletonSet = singleton
+    {-# INLINE singletonSet #-}
+    setFromList = fromFoldable
+    {-# INLINE setFromList #-}
+    setToList = toList
+    {-# INLINE setToList #-}
+    filterSet = filter
+    {-# INLINE filterSet #-}
+
+instance (FiniteBits w, Num w, Enum x, Show x) => Show (EnumSet w x) where
+    showsPrec p xs = showParen (p > 10) $
+        showString "fromList " . shows (toList xs)
+    {-# INLINABLE showsPrec #-}
+
+instance (Bits w, Num w, Enum x, Read x) => Read (EnumSet w x) where
+    readPrec = parens $ prec 10 do
+        Ident "fromList" <- lexP
+        fromFoldable <$> (readPrec :: ReadPrec [x])
+    {-# INLINABLE readPrec #-}
+    readListPrec = readListPrecDefault
+    {-# INLINABLE readListPrec #-}
+
+{--------------------------------------------------------------------
+  Construction
+--------------------------------------------------------------------}
+
+-- | /O(1)/. The empty set.
+empty :: ∀ w a. Bits w
+      => EnumSet w a
+empty = EnumSet zeroBits
+{-# INLINE empty #-}
+
+-- | /O(1)/. A set of one element.
+singleton :: ∀ w a. (Bits w, Enum a)
+          => a -> EnumSet w a
+singleton = EnumSet . bit . fromEnum
+{-# INLINE singleton #-}
+
+-- | /O(n)/. Create a set from a finite foldable data structure.
+fromFoldable :: ∀ f w a. (Foldable f, Bits w, Enum a)
+             => f a -> EnumSet w a
+fromFoldable = EnumSet . F.foldl' (flip $ (.|.) . bit . fromEnum) zeroBits
+
+{--------------------------------------------------------------------
+  Insertion
+--------------------------------------------------------------------}
+
+-- | /O(1)/. Add a value to the set.
+insert :: ∀ w a. (Bits w, Enum a)
+       => a -> EnumSet w a -> EnumSet w a
+insert !x (EnumSet w) = EnumSet . setBit w $ fromEnum x
+
+{--------------------------------------------------------------------
+  Deletion
+--------------------------------------------------------------------}
+
+-- | /O(1)/. Delete a value in the set.
+delete :: ∀ w a. (Bits w, Enum a)
+       => a -> EnumSet w a -> EnumSet w a
+delete !x (EnumSet w) = EnumSet . clearBit w $ fromEnum x
+
+{--------------------------------------------------------------------
+  Query
+--------------------------------------------------------------------}
+
+-- | /O(1)/. Is the value a member of the set?
+member :: ∀ w a. (Bits w, Enum a)
+       => a -> EnumSet w a -> Bool
+member !x (EnumSet w) = testBit w $ fromEnum x
+
+-- | /O(1)/. Is the value not in the set?
+notMember :: ∀ w a. (Bits w, Enum a)
+          => a -> EnumSet w a -> Bool
+notMember !x = not . member x
+
+-- | /O(1)/. Is this the empty set?
+null :: ∀ w a. Bits w
+     => EnumSet w a -> Bool
+null (EnumSet w) = zeroBits == w
+{-# INLINE null #-}
+
+-- | /O(1)/. The number of elements in the set.
+size :: ∀ w a. (Bits w, Num w)
+     => EnumSet w a -> Int
+size (EnumSet !w) = popCount w
+
+-- | /O(1)/. Is this a subset?
+-- @(s1 `isSubsetOf` s2)@ tells whether @s1@ is a subset of @s2@.
+isSubsetOf :: ∀ w a. (Bits w)
+           => EnumSet w a -> EnumSet w a -> Bool
+isSubsetOf (EnumSet x) (EnumSet y) = x .|. y == y
+{-# INLINE isSubsetOf #-}
+
+{--------------------------------------------------------------------
+  Combine
+--------------------------------------------------------------------}
+
+-- | /O(1)/. The union of two sets.
+union :: ∀ w a. Bits w
+      => EnumSet w a -> EnumSet w a -> EnumSet w a
+union (EnumSet x) (EnumSet y) = EnumSet $ x .|. y
+{-# INLINE union #-}
+
+-- | /O(1)/. Difference between two sets.
+difference :: ∀ w a. Bits w
+           => EnumSet w a -> EnumSet w a -> EnumSet w a
+difference (EnumSet x) (EnumSet y) = EnumSet $ (x .|. y) `xor` y
+{-# INLINE difference #-}
+
+-- | /O(1)/. See 'difference'.
+(\\) :: ∀ w a. Bits w
+     => EnumSet w a -> EnumSet w a -> EnumSet w a
+(\\) = difference
+infixl 9 \\
+{-# INLINE (\\) #-}
+
+-- | /O(1)/. Elements which are in either set, but not both.
+symmetricDifference :: ∀ w a. Bits w
+                    => EnumSet w a -> EnumSet w a -> EnumSet w a
+symmetricDifference (EnumSet x) (EnumSet y) = EnumSet $ x `xor` y
+{-# INLINE symmetricDifference #-}
+
+-- | /O(1)/. The intersection of two sets.
+intersection :: ∀ w a. Bits w
+             => EnumSet w a -> EnumSet w a -> EnumSet w a
+intersection (EnumSet x) (EnumSet y) = EnumSet $ x .&. y
+{-# INLINE intersection #-}
+
+{--------------------------------------------------------------------
+  Filter
+--------------------------------------------------------------------}
+
+-- | /O(n)/. Filter all elements that satisfy some predicate.
+filter :: ∀ w a. (FiniteBits w, Num w, Enum a)
+       => (a -> Bool) -> EnumSet w a -> EnumSet w a
+filter p (EnumSet w) = EnumSet $ foldlBits' f 0 w
+    where
+      f z i
+        | p $ toEnum i = setBit z i
+        | otherwise    = z
+      {-# INLINE f #-}
+
+-- | /O(n)/. Partition the set according to some predicate.
+-- The first set contains all elements that satisfy the predicate,
+-- the second all elements that fail the predicate.
+partition :: ∀ w a. (FiniteBits w, Num w, Enum a)
+          => (a -> Bool) -> EnumSet w a -> (EnumSet w a, EnumSet w a)
+partition p (EnumSet w) = (EnumSet yay, EnumSet nay)
+    where
+      (yay, nay) = foldlBits' f (0, 0) w
+      f (x, y) i
+          | p $ toEnum i = (setBit x i, y)
+          | otherwise    = (x, setBit y i)
+      {-# INLINE f #-}
+
+{--------------------------------------------------------------------
+  Map
+--------------------------------------------------------------------}
+
+-- | /O(n)/.
+-- @'map' f s@ is the set obtained by applying @f@ to each element of @s@.
+--
+-- It's worth noting that the size of the result may be smaller if,
+-- for some @(x,y)@, @x \/= y && f x == f y@.
+map :: ∀ w a b. (FiniteBits w, Num w, Enum a, Enum b)
+    => (a -> b) -> EnumSet w a -> EnumSet w b
+map = map'
+{-# INLINE map #-}
+
+-- | /O(n)/. Apply 'map' while converting the underlying representation of the
+-- set to some other representation.
+map' :: ∀ v w a b. (FiniteBits v, FiniteBits w, Num v, Num w, Enum a, Enum b)
+     => (a -> b) -> EnumSet v a -> EnumSet w b
+map' f0 (EnumSet w) = EnumSet $ foldlBits' f 0 w
+    where
+      f z i = setBit z $ fromEnum $ f0 (toEnum i)
+      {-# INLINE f #-}
+
+{--------------------------------------------------------------------
+  Folds
+--------------------------------------------------------------------}
+
+-- | /O(n)/. Left fold.
+foldl :: ∀ w a b. (FiniteBits w, Num w, Enum a)
+      => (b -> a -> b) -> b -> EnumSet w a -> b
+foldl f z (EnumSet w) = foldlBits ((. toEnum) . f) z w
+{-# INLINE foldl #-}
+
+-- | /O(n)/. Left fold with strict accumulator.
+foldl' :: ∀ w a b. (FiniteBits w, Num w, Enum a)
+       => (b -> a -> b) -> b -> EnumSet w a -> b
+foldl' f z (EnumSet w) = foldlBits' ((. toEnum) . f) z w
+{-# INLINE foldl' #-}
+
+-- | /O(n)/. Right fold.
+foldr :: ∀ w a b. (FiniteBits w, Num w, Enum a)
+      => (a -> b -> b) -> b -> EnumSet w a -> b
+foldr f z (EnumSet w) = foldrBits (f . toEnum) z w
+{-# INLINE foldr #-}
+
+-- | /O(n)/. Right fold with strict accumulator.
+foldr' :: ∀ w a b. (FiniteBits w, Num w,  Enum a)
+       => (a -> b -> b) -> b -> EnumSet w a -> b
+foldr' f z (EnumSet w) = foldrBits' (f . toEnum) z w
+{-# INLINE foldr' #-}
+
+-- | /O(n)/. Left fold on non-empty sets.
+foldl1 :: ∀ w a. (FiniteBits w, Num w, Enum a)
+       => (a -> a -> a) -> EnumSet w a -> a
+foldl1 f = fold1Aux lsb $ foldlBits ((. toEnum) . f)
+{-# INLINE foldl1 #-}
+
+-- | /O(n)/. Left fold on non-empty sets with strict accumulator.
+foldl1' :: ∀ w a. (FiniteBits w, Num w, Enum a)
+        => (a -> a -> a) -> EnumSet w a -> a
+foldl1' f = fold1Aux lsb $ foldlBits' ((.toEnum) . f)
+{-# INLINE foldl1' #-}
+
+-- | /O(n)/. Right fold on non-empty sets.
+foldr1 :: ∀ w a. (FiniteBits w, Num w, Enum a)
+       => (a -> a -> a) -> EnumSet w a -> a
+foldr1 f = fold1Aux msb $ foldrBits (f . toEnum)
+{-# INLINE foldr1 #-}
+
+-- | /O(n)/. Right fold on non-empty sets with strict accumulator.
+foldr1' :: ∀ w a. (FiniteBits w, Num w, Enum a)
+        => (a -> a -> a) -> EnumSet w a -> a
+foldr1' f = fold1Aux msb $ foldrBits' (f . toEnum)
+{-# INLINE foldr1' #-}
+
+-- | /O(n)/. Map each element of the structure to a monoid, and combine the
+-- results.
+foldMap :: ∀ m w a. (Monoid m, FiniteBits w, Num w, Enum a)
+        => (a -> m) -> EnumSet w a -> m
+foldMap f (EnumSet w) = foldrBits (mappend . f . toEnum) mempty w
+{-# INLINE foldMap #-}
+
+traverse :: ∀ f w a. (Applicative f, FiniteBits w, Num w, Enum a)
+         => (a -> f a) -> EnumSet w a -> f (EnumSet w a)
+traverse f (EnumSet w) = EnumSet <$>
+                         foldrBits
+                         (liftA2 (flip setBit) . fmap fromEnum . f . toEnum)
+                         (pure zeroBits)
+                         w
+{-# INLINE traverse #-}
+
+-- | /O(n)/. Check if all elements satisfy some predicate.
+all :: ∀ w a. (FiniteBits w, Num w, Enum a)
+    => (a -> Bool) -> EnumSet w a -> Bool
+all p (EnumSet w) = let lb = lsb w in go lb (w `unsafeShiftR` lb)
+  where
+    go !_ 0 = True
+    go bi n
+      | n `testBit` 0 && not (p $ toEnum bi) = False
+      | otherwise = go (bi + 1) (n `unsafeShiftR` 1)
+
+-- | /O(n)/. Check if any element satisfies some predicate.
+any :: ∀ w a. (FiniteBits w, Num w, Enum a)
+    => (a -> Bool) -> EnumSet w a -> Bool
+any p (EnumSet w) = let lb = lsb w in go lb (w `unsafeShiftR` lb)
+  where
+    go !_ 0 = False
+    go bi n
+      | n `testBit` 0 && p (toEnum bi) = True
+      | otherwise = go (bi + 1) (n `unsafeShiftR` 1)
+
+{--------------------------------------------------------------------
+  Min/Max
+--------------------------------------------------------------------}
+
+-- | /O(1)/. The minimal element of a non-empty set.
+minimum :: ∀ w a. (FiniteBits w, Num w, Enum a)
+        => EnumSet w a -> a
+minimum (EnumSet 0) = error "empty EnumSet"
+minimum (EnumSet w) = toEnum $ lsb w
+
+-- | /O(1)/. The maximal element of a non-empty set.
+maximum :: ∀ w a. (FiniteBits w, Num w, Enum a)
+        => EnumSet w a -> a
+maximum (EnumSet 0) = error "empty EnumSet"
+maximum (EnumSet w) = toEnum $ msb w
+
+-- | /O(1)/. Delete the minimal element.
+deleteMin :: ∀ w a. (FiniteBits w, Num w)
+          => EnumSet w a -> EnumSet w a
+deleteMin (EnumSet 0) = EnumSet 0
+deleteMin (EnumSet w) = EnumSet $ clearBit w $ lsb w
+
+-- | /O(1)/. Delete the maximal element.
+deleteMax :: ∀ w a. (FiniteBits w, Num w)
+          => EnumSet w a -> EnumSet w a
+deleteMax (EnumSet 0) = EnumSet 0
+deleteMax (EnumSet w) = EnumSet $ clearBit w $ msb w
+
+-- | /O(1)/. Retrieves the minimal element of the set,
+-- and the set stripped of that element,
+-- or Nothing if passed an empty set.
+minView :: ∀ w a. (FiniteBits w, Num w, Enum a)
+        => EnumSet w a -> Maybe (a, EnumSet w a)
+minView (EnumSet 0) = Nothing
+minView (EnumSet w) = let i = lsb w in Just (toEnum i, EnumSet $ clearBit w i)
+
+-- | /O(1)/. Retrieves the maximal element of the set,
+-- and the set stripped of that element,
+-- or Nothing if passed an empty set.
+maxView :: ∀ w a. (FiniteBits w, Num w, Enum a)
+        => EnumSet w a -> Maybe (a, EnumSet w a)
+maxView (EnumSet 0) = Nothing
+maxView (EnumSet w) = let i = msb w in Just (toEnum i, EnumSet $ clearBit w i)
+
+{--------------------------------------------------------------------
+  Conversion
+--------------------------------------------------------------------}
+
+-- | /O(n)/. Convert the set to a list of values.
+toList :: ∀ w a. (FiniteBits w, Num w, Enum a)
+       => EnumSet w a -> [a]
+toList (EnumSet w) = build \c n -> foldrBits (c . toEnum) n w
+{-# INLINE toList #-}
+
+-- | /O(1)/. Convert a representation into an @EnumSet@.
+-- Intended for use with foreign types.
+fromRaw :: ∀ w a. w -> EnumSet w a
+fromRaw = EnumSet
+{-# INLINE fromRaw #-}
+
+{--------------------------------------------------------------------
+  Utility functions
+--------------------------------------------------------------------}
+
+lsb :: ∀ w. (FiniteBits w, Num w) => w -> Int
+lsb n0 = go 0 n0 $ finiteBitSize n0 `quot` 2
+  where
+    go b n 1 = case n .&. 1 of
+        0 -> 1 + b
+        _ -> b
+    go b n i = case n .&. (bit i - 1) of
+        0 -> go (i + b) (n `unsafeShiftR` i) (i `quot` 2)
+        _ -> go b       n                    (i `quot` 2)
+{-# INLINE lsb #-}
+
+msb :: ∀ w. (FiniteBits w, Num w) => w -> Int
+msb n0 = go 0 n0 $ finiteBitSize n0 `quot` 2
+  where
+    go b n 1 = case n .&. 2 of
+        0 -> b
+        _ -> 1 + b
+    go b n i = case n .&. (bit (i * 2) - bit i) of
+        0 -> go b       n                    (i `quot` 2)
+        _ -> go (i + b) (n `unsafeShiftR` i) (i `quot` 2)
+{-# INLINE msb #-}
+
+foldlBits :: ∀ w a. (FiniteBits w, Num w) => (a -> Int -> a) -> a -> w -> a
+foldlBits  f z w = let lb = lsb w in go lb z (w `unsafeShiftR` lb)
+  where
+    go !_ acc 0 = acc
+    go bi acc n
+      | n `testBit` 0 = go (bi + 1) (f acc bi) (n `unsafeShiftR` 1)
+      | otherwise     = go (bi + 1)    acc     (n `unsafeShiftR` 1)
+{-# INLINE foldlBits #-}
+
+foldlBits' :: ∀ w a. (FiniteBits w, Num w) => (a -> Int -> a) -> a -> w -> a
+foldlBits' f z w = let lb = lsb w in go lb z (w `unsafeShiftR` lb)
+  where
+    go !_ !acc 0 = acc
+    go bi acc n
+      | n `testBit` 0 = go (bi + 1) (f acc bi) (n `unsafeShiftR` 1)
+      | otherwise     = go (bi + 1)    acc     (n `unsafeShiftR` 1)
+{-# INLINE foldlBits' #-}
+
+foldrBits :: ∀ w a. (FiniteBits w, Num w) => (Int -> a -> a) -> a -> w -> a
+foldrBits f z w = let lb = lsb w in go lb (w `unsafeShiftR` lb)
+  where
+    go !_ 0 = z
+    go bi n
+      | n `testBit` 0 = f bi (go (bi + 1) (n `unsafeShiftR` 1))
+      | otherwise     =       go (bi + 1) (n `unsafeShiftR` 1)
+{-# INLINE foldrBits #-}
+
+foldrBits' :: ∀ w a. (FiniteBits w, Num w) => (Int -> a -> a) -> a -> w -> a
+foldrBits' f z w = let lb = lsb w in go lb (w `unsafeShiftR` lb)
+  where
+    go !_ 0 = z
+    go bi n
+      | n `testBit` 0 = f bi $! go (bi + 1) (n `unsafeShiftR` 1)
+      | otherwise     =         go (bi + 1) (n `unsafeShiftR` 1)
+{-# INLINE foldrBits' #-}
+
+fold1Aux :: ∀ w a. (Bits w, Num w, Enum a)
+         => (w -> Int) -> (a -> w -> a) -> EnumSet w a -> a
+fold1Aux _      _ (EnumSet 0) = error "empty EnumSet"
+fold1Aux getBit f (EnumSet w) = f (toEnum gotBit) (clearBit w gotBit)
+  where
+    gotBit = getBit w
+{-# INLINE fold1Aux #-}
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, Joshua Booth
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Johan Tibell nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,2 @@+# bitwise-enum+Bitwise operations on bounded enumerations.
+ Setup.hs view
@@ -0,0 +1,4 @@+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
+ benchmarks/EnumSet.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE ExplicitForAll      #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications    #-}
+{-# LANGUAGE UnicodeSyntax       #-}
+
+module Main where
+
+import Prelude
+
+import Control.DeepSeq (NFData, rnf)
+import Control.Exception (evaluate)
+import Data.Bits
+import Data.List (foldl', transpose)
+import Data.WideWord (Word128)
+import Data.Word (Word16, Word64)
+import qualified Gauge as G
+import Gauge (Benchmark)
+import Type.Reflection
+
+import qualified Data.Enum.Set.Base as E
+
+main :: IO ()
+main = G.defaultMain . concat . transpose =<< sequence
+    [ benchWord @Word
+    , benchWord @Word16
+    , benchWord @Word64
+    , benchWord @Word128
+    ]
+
+benchWord :: ∀ w. (FiniteBits w, NFData w, Num w, Typeable w) => IO [Benchmark]
+benchWord = do
+    let s = E.fromFoldable elems :: E.EnumSet w Int
+        s_even = E.fromFoldable elems_even :: E.EnumSet w Int
+        s_odd = E.fromFoldable elems_odd :: E.EnumSet w Int
+    evaluate $ rnf [s, s_even, s_odd]
+    return
+      [ bench "singleton" (E.singleton :: Int -> E.EnumSet w Int) 2
+      , bench "fromFoldable" (E.fromFoldable :: [Int] -> E.EnumSet w Int) elems
+      , bench "insert" (ins elems) (E.empty :: E.EnumSet w Int)
+      , bench "delete" (del elems) s
+      , bench "member" (member elems) s
+      , bench "notMember" (notMember elems) s
+      , bench "null" E.null s
+      , bench "size" E.size s
+      , bench "isSubsetOf" (E.isSubsetOf s) s_even
+      , bench "union" (E.union s_even) s_odd
+      , bench "difference" (E.difference s) s_even
+      , bench "symmetricDifference" (E.symmetricDifference s) s_even
+      , bench "intersection" (E.intersection s) s_even
+      , bench "null.intersection:false" (E.null . E.intersection s) s_even
+      , bench "null.intersection:true" (E.null . E.intersection s_odd) s_even
+      , bench "filter" (E.filter ((== 0) . (`mod` 2))) s
+      , bench "partition" (E.partition ((== 0) . (`mod` 2))) s
+      , bench "map" (E.map (+1)) s
+      , bench "foldl" (E.foldl (flip (:)) []) s
+      , bench "foldl'" (E.foldl' (flip (:)) []) s
+      , bench "foldr" (E.foldr (:) []) s
+      , bench "foldr'" (E.foldr' (:) []) s
+      , bench "foldl1" (E.foldl1 (+)) s
+      , bench "foldl1'" (E.foldl1' (+)) s
+      , bench "foldr1" (E.foldr1 (+)) s
+      , bench "foldr1'" (E.foldr1' (+)) s
+      , bench "foldMap" (return :: ∀ a. a -> [a]) s
+      , bench "traverse" (return :: ∀ a. a -> [a]) s
+      , bench "all" (E.all (/= -1)) s
+      , bench "any" (E.any (== -1)) s
+      , bench "minimum" E.minimum s
+      , bench "maximum" E.maximum s
+      , bench "deleteMin" E.deleteMin s
+      , bench "deleteMax" E.deleteMax s
+      , bench "minView" E.minView s
+      , bench "maxView" E.maxView s
+      , bench "toList" E.toList s
+      ]
+  where
+    maxVal = 15
+    elems = [0..maxVal]
+    elems_even = [2,4..maxVal]
+    elems_odd = [1,3..maxVal]
+    prefix = show (typeRep @w) ++ " "
+    bench :: String -> (a -> b) -> a -> G.Benchmark
+    bench label f = G.bench (prefix ++ label) . G.whnf f
+
+member :: ∀ w a. (Bits w, Enum a) => [a] -> E.EnumSet w a -> Int
+member xs s = foldl' (\n x -> if E.member x s then n + 1 else n) 0 xs
+
+notMember :: ∀ w a. (Bits w, Enum a) => [a] -> E.EnumSet w a -> Int
+notMember xs s = foldl' (\n x -> if E.notMember x s then n + 1 else n) 0 xs
+
+ins :: ∀ w a. (Bits w, Enum a) => [a] -> E.EnumSet w a -> E.EnumSet w a
+ins xs s = foldl' (flip E.insert) s xs
+
+del :: ∀ w a. (Bits w, Enum a) => [a] -> E.EnumSet w a -> E.EnumSet w a
+del xs s = foldl' (flip E.delete) s xs
+ bitwise-enum.cabal view
@@ -0,0 +1,80 @@+cabal-version: 1.12++name:           bitwise-enum+version:        0.1.0.0+synopsis:       Bitwise operations on bounded enumerations+description:+    Bitwise operations on bounded enumerations.+    .+    ["Data.Enum.Set"] Constant-time sets using bit flags.+    .+    ["Data.Enum.Memo"] Constant-time lookup memoization for functions on enumerated types.+homepage:       https://github.com/jnbooth/bitwise-enum#readme+bug-reports:    https://github.com/jnbooth/bitwise-enum/issues+author:         Joshua Booth <joshua.n.booth@gmail.com>+maintainer:     Joshua Booth <joshua.n.booth@gmail.com>+license:        BSD3+license-file:   LICENSE+build-type:     Simple++source-repository head+  type: git+  location: https://github.com/jnbooth/bitwise-enum++library+  exposed-modules:+      Data.Enum.Memo+      Data.Enum.Set+      Data.Enum.Set.Base+  other-modules:+      Paths_bounded_enum+  hs-source-dirs:+      ./+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-record-updates -ferror-spans -funbox-small-strict-fields -O2+  build-depends:+      aeson >=0.11 && <1.4.7+    , array >=0.5.1 && <0.5.5+    , base >=4.5 && <5+    , deepseq >=1.1 && <1.4.5+    , mono-traversable >=1.0.12 && <1.0.16+    , vector >=0.11 && <0.12.1+  default-language: Haskell2010++test-suite enumset-test-suite+  type: exitcode-stdio-1.0+  main-is: set-properties.hs+  other-modules:+      Paths_bounded_enum+  hs-source-dirs:+      tests+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-record-updates -ferror-spans -funbox-small-strict-fields+  build-depends:+      QuickCheck >=2.13.2+    , aeson >=0.11 && <1.4.7+    , base+    , bitwise-enum+    , deepseq >=1.1 && <1.4.5+    , mono-traversable >=1.0.12 && <1.0.16+    , test-framework >=0.8.2.0+    , test-framework-quickcheck2 >=0.3.0.5+    , vector >=0.11 && <0.12.1+  default-language: Haskell2010++benchmark enumset-benchmarks+  type: exitcode-stdio-1.0+  main-is: EnumSet.hs+  other-modules:+      Paths_bounded_enum+  hs-source-dirs:+      benchmarks+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-record-updates -ferror-spans -funbox-small-strict-fields -rtsopts -threaded -with-rtsopts=-N -O2+  build-depends:+      aeson >=0.11 && <1.4.7+    , base+    , bitwise-enum+    , deepseq >=1.1 && <1.4.5+    , gauge >=0.2.5+    , mono-traversable >=1.0.12 && <1.0.16+    , vector >=0.11 && <0.12.1+    , wide-word >=0.1.0.9+  default-language: Haskell2010
+ tests/set-properties.hs view
@@ -0,0 +1,236 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+import Prelude
+
+import Data.List ((\\), foldl', nub, sort)
+import Data.Bits
+import Test.Framework
+import Test.Framework.Providers.QuickCheck2
+import Test.QuickCheck
+import Test.QuickCheck.Function
+import Unsafe.Coerce (unsafeCoerce)
+
+import qualified Data.Enum.Set.Base as E
+
+main :: IO ()
+main = defaultMain
+    [ -- Construction
+      testProperty "singleton" prop_singleton
+    , testProperty "fromFoldable" prop_fromFoldable
+      -- Insertion/Deletion
+    , testProperty "insert/delete" prop_insertDelete
+      -- Query
+    , testProperty "member" prop_member
+    , testProperty "notMember" prop_notMember
+    , testProperty "size" prop_size
+    , testProperty "isSubsetOf" prop_isSubsetOf
+      -- Combine
+    , testProperty "unionInsert" prop_unionInsert
+    , testProperty "unionAssoc" prop_unionAssoc
+    , testProperty "unionComm" prop_unionComm
+    , testProperty "difference" prop_difference
+    , testProperty "intersection" prop_intersection
+      -- Filter
+    , testProperty "filter" prop_filter
+    , testProperty "partition" prop_partition
+      -- Map
+    , testProperty "map" prop_map
+    , testProperty "map2" prop_map2
+      -- Folds
+    , testProperty "foldl" prop_foldl
+    , testProperty "foldl'" prop_foldl'
+    , testProperty "foldr" prop_foldr
+    , testProperty "foldr'" prop_foldr'
+      -- Special folds
+    , testProperty "foldMap" prop_foldMap
+    , testProperty "traverse" prop_traverse
+    , testProperty "any" prop_any
+    , testProperty "all" prop_all
+      -- Min/Max
+    , testProperty "minimum" prop_minimum
+    , testProperty "maximum" prop_maximum
+    , testProperty "minView" prop_minView
+    , testProperty "maxView" prop_maxView
+      -- Conversion
+    , testProperty "toList" prop_toList
+    , testProperty "Read/Show" prop_readShow
+    ]
+
+wordSize :: Int
+wordSize = finiteBitSize (0 :: Word) - 1
+
+newtype Key = Key Int deriving (Eq, Ord, Show, Read, Real, Integral)
+
+instance Enum Key where
+    toEnum x
+      | x < 0 || x > wordSize = error $ "Key.toEnum: bad argument " ++ show x
+      | otherwise             = Key x
+    fromEnum (Key x) = x
+
+instance Num Key where
+    (Key x) + (Key y) = Key . min wordSize $ x + y
+    (Key x) - (Key y) = Key . max 0 $ x - y
+    (Key x) * (Key y) = Key . min wordSize $ x + y
+    negate = id
+    abs = id
+    signum (Key 0) = 0
+    signum _       = 1
+    fromInteger = toEnum . max 0 . min wordSize . fromInteger
+
+instance Bounded Key where
+    minBound = 0
+    maxBound = Key $ finiteBitSize (0 :: Word) - 1
+
+instance Arbitrary Key where
+    arbitrary = arbitrarySizedBoundedIntegral
+    shrink    = shrinkIntegral
+
+instance Function Key where
+    function = functionIntegral
+
+instance CoArbitrary Key where
+    coarbitrary = coarbitraryIntegral
+
+type ES = E.EnumSet Word Key
+
+fromBits :: w -> E.EnumSet w a
+fromBits = unsafeCoerce
+
+toBits :: E.EnumSet w a -> w
+toBits = unsafeCoerce
+
+instance Arbitrary w => Arbitrary (E.EnumSet w a) where
+    arbitrary = fmap fromBits arbitrary
+    shrink = fmap fromBits . shrink . toBits
+
+-- * Construction
+
+prop_singleton :: Key -> Bool
+prop_singleton x = E.insert x (E.empty :: ES) == E.singleton x
+
+prop_fromFoldable :: [Key] -> Property
+prop_fromFoldable xs =
+    (E.fromFoldable xs :: ES) === foldr E.insert E.empty xs
+
+-- * Insertion/Deletion
+
+prop_insertDelete :: Key -> ES -> Property
+prop_insertDelete k t = not (E.member k t) ==> E.delete k (E.insert k t) == t
+
+-- * Query
+
+prop_member :: [Key] -> Key -> Bool
+prop_member xs n =
+    let m = E.fromFoldable xs :: ES
+    in all (\k -> k `E.member` m == (k `elem` xs)) (n : xs)
+
+prop_notMember :: [Key] -> Key -> Bool
+prop_notMember xs n =
+    let m = E.fromFoldable xs :: ES
+    in all (\k -> k `E.notMember` m == (k `notElem` xs)) (n : xs)
+
+prop_size :: ES -> Bool
+prop_size s = E.size s == length (E.toList s)
+
+-- TODO isSubsetOf
+
+prop_isSubsetOf :: ES -> ES -> Bool
+prop_isSubsetOf x y = x `E.isSubsetOf` y == all (`elem` E.toList y) (E.toList x)
+
+-- * Combine
+
+prop_unionInsert :: Key -> ES -> Bool
+prop_unionInsert x t = E.union t (E.singleton x) == E.insert x t
+
+prop_unionAssoc :: ES -> ES -> ES -> Bool
+prop_unionAssoc x y z = E.union x (E.union y z) == E.union (E.union x y) z
+
+prop_unionComm :: ES -> ES -> Bool
+prop_unionComm x y = E.union x y == E.union y x
+
+prop_difference :: [Key] -> [Key] -> Bool
+prop_difference xs ys =
+    (E.toList :: ES -> [Key])
+    (E.difference (E.fromFoldable xs) (E.fromFoldable ys))
+    == sort ((\\) (nub xs) (nub ys))
+
+prop_intersection :: ES -> ES -> Bool
+prop_intersection x y =
+    x `E.intersection` y == E.fromFoldable (filter (`elem` E.toList x) (E.toList y))
+
+-- * Filter
+
+prop_filter :: ES -> Bool
+prop_filter s = E.partition odd s == (E.filter odd s, E.filter even s)
+
+prop_partition :: ES -> Bool
+prop_partition s = case E.partition odd s of
+    (s1,s2) -> all odd (E.toList s1) && all even (E.toList s2)
+               && s == s1 `E.union` s2
+
+-- * Map
+
+prop_map :: ES -> Bool
+prop_map s = E.map id s == s
+
+prop_map2 :: Fun Key Key -> Fun Key Key -> ES -> Property
+prop_map2 f g s =
+    E.map (apply f) (E.map (apply g) s) === E.map (apply f . apply g) s
+
+-- * Folds
+
+prop_foldl :: ES -> Bool
+prop_foldl s = E.foldl (flip (:)) [] s == foldl (flip (:)) [] (E.toList s)
+
+prop_foldl' :: ES -> Bool
+prop_foldl' s = E.foldl' (flip (:)) [] s == foldl' (flip (:)) [] (E.toList s)
+
+prop_foldr :: ES -> Bool
+prop_foldr s = E.foldr (:) [] s == E.toList s
+
+prop_foldr' :: ES -> Bool
+prop_foldr' s = E.foldr' (:) [] s == E.toList s
+
+-- * Special folds
+
+prop_all :: Fun Key Bool -> ES -> Property
+prop_all p s = E.all (apply p) s === all (apply p) (E.toList s)
+
+prop_any :: Fun Key Bool -> ES -> Property
+prop_any p s = E.any (apply p) s === any (apply p) (E.toList s)
+
+prop_foldMap :: ES -> Bool
+prop_foldMap s = E.foldMap return s == foldMap (: []) (E.toList s)
+
+prop_traverse :: ES -> Bool
+prop_traverse s = map E.toList (E.traverse return s) == traverse (: []) (E.toList s)
+
+
+-- * Min/Max
+
+prop_minimum :: ES -> Property
+prop_minimum s = not (E.null s) ==> E.minimum s == minimum (E.toList s)
+
+prop_maximum :: ES -> Property
+prop_maximum s = not (E.null s) ==> E.maximum s == maximum (E.toList s)
+
+prop_minView :: ES -> Bool
+prop_minView s = case E.minView s of
+    Nothing -> E.null s
+    Just (m,s') -> m == minimum (E.toList s)
+                   && s == E.insert m s' && m `E.notMember` s'
+
+prop_maxView :: ES -> Bool
+prop_maxView s = case E.maxView s of
+    Nothing -> E.null s
+    Just (m,s') -> m == maximum (E.toList s)
+                   && s == E.insert m s' && m `E.notMember` s'
+
+-- * Conversion
+
+prop_toList :: [Key] -> Bool
+prop_toList xs = sort (nub xs) == E.toList (E.fromFoldable xs :: ES)
+
+prop_readShow :: ES -> Bool
+prop_readShow s = s == read (show s)