{-# LANGUAGE BangPatterns , RankNTypes , UnicodeSyntax #-} -- | Generic interface to diverse types of 'Bitstream'. module Data.Bitstream.Generic ( -- * The type class Bitstream(..) -- * Introducing and eliminating 'Bitstream's , empty , (∅) , singleton , pack , unpack -- ** Converting from\/to 'Bits'' , fromBits , fromNBits , toBits -- ** Converting from\/to 'S.Stream's , stream , unstream -- * Basic interface , cons , cons' , snoc , append , (⧺) , head , last , tail , init , null , length -- * Transforming 'Bitstream's , map , reverse -- * Reducing 'Bitstream's , foldl , foldl' , foldl1 , foldl1' , foldr , foldr1 -- ** Special folds , concat , concatMap , and , or , any , all -- * Building 'Bitstream's -- ** scans , scanl , scanl1 , scanr , scanr1 -- ** Replication , replicate -- ** Unfolding , unfoldr , unfoldrN -- * Substreams , take , drop , takeWhile , dropWhile , span , break -- * Searching streams , elem , (∈) , (∋) , notElem , (∉) , (∌) -- ** Searching with a predicate , find , filter , partition -- ** Indexing streams , (!!) , elemIndex , elemIndices , findIndex , findIndices -- * Zipping and unzipping streams , zip , zip3 , zip4 , zip5 , zip6 , zipWith , zipWith3 , zipWith4 , zipWith5 , zipWith6 , unzip , unzip3 , unzip4 , unzip5 , unzip6 ) where import qualified Data.List as L import Data.Bits import Data.Bitstream.Fusion import Data.Maybe import Data.Vector.Fusion.Stream (Stream) import qualified Data.Vector.Fusion.Stream as S import Prelude ( Bool(..), Integer, Integral(..), Num(..), Show(..), ($) , fst, flip, otherwise, snd ) import Prelude.Unicode hiding ((∈), (∉), (⧺)) infix 4 ∈, ∋, ∉, ∌, `elem`, `notElem` infixr 5 ⧺, `append` infixl 9 !! {- Notes about inlining / rewriting phase control: 1. We want "*/unstream fusion" rules always fire. 2. Fusible producer inlinings should always occur. 3. Unfused form specialisations should occur at phase 1 and later. 4. Fusible consumer/filter inlinings should occur last i.e. phase 0. 5. stream/unstream inlinings should never occur. -} -- | Class of diverse types of 'Bitstream'. -- -- Methods of this class are functions of 'Bitstream's that are either -- basic functions to implement other ones, or have to preserve their -- packet/chunk structure for efficiency and strictness behaviour. -- -- Minimum complete implementation: /All but/ 'basicCons'', -- 'basicConcat', 'basicReplicate', 'basicPartition' and -- 'basicFromBits'. class Bitstream α where basicStream ∷ α → Stream Bool basicUnstream ∷ Stream Bool → α basicCons ∷ Bool → α → α basicCons' ∷ Bool → α → α {-# INLINE basicCons' #-} basicCons' = basicCons basicSnoc ∷ α → Bool → α basicAppend ∷ α → α → α basicTail ∷ α → α basicInit ∷ α → α basicMap ∷ (Bool → Bool) → α → α basicReverse ∷ α → α basicConcat ∷ [α] → α {-# INLINE basicConcat #-} basicConcat [] = (∅) basicConcat (α:αs) = α ⧺ concat αs basicScanl ∷ (Bool → Bool → Bool) → Bool → α → α basicTake ∷ Integral n ⇒ n → α → α basicDrop ∷ Integral n ⇒ n → α → α basicTakeWhile ∷ (Bool → Bool) → α → α basicDropWhile ∷ (Bool → Bool) → α → α basicFilter ∷ (Bool → Bool) → α → α basicPartition ∷ (Bool → Bool) → α → (α, α) {-# INLINE basicPartition #-} basicPartition f α = (filter f α, filter ((¬) ∘ f) α) basicFromNBits ∷ (Integral n, Integral β, Bits β) ⇒ n → β → α basicToBits ∷ Bits β ⇒ α → β -- | /O(1)/ The empty 'Bitstream'. empty ∷ Bitstream α ⇒ α {-# INLINE empty #-} empty = unstream S.empty -- | (∅) = 'empty' -- -- U+2205, EMPTY SET {-# INLINE (∅) #-} (∅) ∷ Bitstream α ⇒ α (∅) = empty -- | /O(1)/ Convert a 'Bool' into a 'Bitstream'. singleton ∷ Bitstream α ⇒ Bool → α {-# INLINE singleton #-} singleton = unstream ∘ S.singleton -- | /O(n)/ Convert a ['Bool'] into a 'Bitstream'. {-# INLINE pack #-} pack ∷ Bitstream α ⇒ [Bool] → α pack = unstream ∘ S.fromList -- | /O(n)/ Convert a 'Bitstream' into a ['Bool']. unpack ∷ Bitstream α ⇒ α → [Bool] {-# RULES "Bitstream unpack/unstream fusion" ∀s. unpack (unstream s) = S.toList s #-} {-# INLINE [0] unpack #-} unpack = S.toList ∘ stream -- | /O(n)/ Explicitly convert a 'Bitstream' into a 'Stream' of -- 'Bool'. -- -- 'Bitstream' operations are automatically fused whenever it's -- possible, safe, and effective to do so, but sometimes you may find -- the rules are too conservative. These two functions 'stream' and -- 'unstream' provide a means for coercive stream fusion. -- -- You should be careful when you use 'stream'. Most functions in this -- package are optimised to minimise frequency of memory allocations -- and copyings, but getting 'Bitstream's back from @'Stream' 'Bool'@ -- requires the whole 'Bitstream' to be constructed from -- scratch. Moreover, for lazy 'Bitstream's this leads to be an -- incorrect strictness behaviour because lazy 'Bitstream's are -- represented as lists of strict 'Bitstream' chunks but 'stream' -- can't preserve the original chunk structure. Let's say you have a -- lazy 'Bitstream' with the following chunks: -- -- @ -- bs = [chunk1, chunk2, chunk3, ...] -- @ -- -- and you want to drop the first bit of such stream. Our 'tail' is -- only strict on the @chunk1@ and will produce the following chunks: -- -- @ -- 'tail' bs = [chunk0, chunk1', chunk2, chunk3, ...] -- @ -- -- where @chunk0@ is a singleton vector of the first packet of -- @chunk1@ whose first bit is dropped, and @chunk1'@ is a vector of -- remaining packets of the @chunk1@. Neither @chunk2@ nor @chunk3@ -- have to be evaluated here as you might expect. -- -- But think about the following expression: -- -- @ -- import qualified Data.Vector.Fusion.Stream as Stream -- 'unstream' $ Stream.tail $ 'stream' bs -- @ -- -- the resulting chunk structure will be: -- -- @ -- [chunk1', chunk2', chunk3', ...] -- @ -- -- where each and every chunks are slightly different from the -- original chunks, and this time @chunk1'@ has the same length as -- @chunk1@ but the last bit of @chunk1'@ is from the first bit of -- @chunk2@. This means when you next time apply some functions strict -- on the first chunk, you end up fully evaluating @chunk2@ as well as -- @chunk1@ and this can be a serious misbehaviour for lazy -- 'Bitstream's. -- -- The automatic fusion rules are carefully designed to fire only when -- there aren't any reason to preserve the original packet / chunk -- structure. stream ∷ Bitstream α ⇒ α → Stream Bool {-# NOINLINE stream #-} stream = basicStream -- | /O(n)/ Convert a 'S.Stream' of 'Bool' into a 'Bitstream'. unstream ∷ Bitstream α ⇒ Stream Bool → α {-# NOINLINE unstream #-} unstream = basicUnstream {-# RULES "Bitstream stream/unstream fusion" ∀s. stream (unstream s) = s "Bitstream unstream/stream fusion" ∀v. unstream (stream v) = v #-} -- | /O(n)/ Convert a 'Bits' into a 'Bitstream'. Note that this -- function is undefined for instances of 'Bits' which have no fixed -- 'bitSize' (like 'Integer'). fromBits ∷ (Integral β, Bits β, Bitstream α) ⇒ β → α {-# INLINE fromBits #-} fromBits β = basicFromNBits (bitSize β) β -- | /O(n)/ Convert the lower 'n' bits of the given 'Bits'. In the -- case that more bits are requested than the 'Bits' provides, this -- acts as if the 'Bits' has an infinite number of leading 0 bits. fromNBits ∷ (Integral n, Integral β, Bits β, Bitstream α) ⇒ n → β → α {-# INLINE fromNBits #-} fromNBits = basicFromNBits -- | /O(n)/ Convert a 'Bitstream' into a 'Bits'. toBits ∷ (Bitstream α, Bits β) ⇒ α → β {-# INLINE [0] toBits #-} toBits = basicToBits -- | /strict: O(n), lazy: O(1)/ 'cons' is an analogous to (':') for -- lists. cons ∷ Bitstream α ⇒ Bool → α → α {-# RULES "Bitstream cons/unstream fusion" ∀b s. cons b (unstream s) = unstream (S.cons b s) #-} {-# INLINE [0] cons #-} cons = basicCons -- | /O(n)/ For strict 'Bitstream's, 'cons'' is exactly the same as -- 'cons'. -- -- For lazy ones, 'cons'' is strict in the 'Bitstream' we are consing -- onto. More precisely, it forces the first chunk to be evaluated. It -- does this because, for space efficiency, it may coalesce the new -- bit onto the first chunk rather than starting a new chunk. cons' ∷ Bitstream α ⇒ Bool → α → α {-# RULES "Bitstream cons'/unstream fusion" ∀b s. cons' b (unstream s) = unstream (S.cons b s) #-} {-# INLINE [0] cons' #-} cons' = basicCons' -- | /O(n)/ Append a bit to the end of a 'Bitstream'. snoc ∷ Bitstream α ⇒ α → Bool → α {-# RULES "Bitstream snoc/unstream fusion" ∀s b. snoc (unstream s) b = unstream (S.snoc s b) #-} {-# INLINE [0] snoc #-} snoc = basicSnoc -- | /O(n)/ Append two 'Bitstream's. append ∷ Bitstream α ⇒ α → α → α {-# RULES "Bitstream append/unstream fusion" ∀s1 s2. append (unstream s1) (unstream s2) = unstream (s1 S.++ s2) #-} {-# INLINE [0] append #-} append = basicAppend -- | (⧺) = 'append' -- -- U+29FA, DOUBLE PLUS (⧺) ∷ Bitstream α ⇒ α → α → α {-# INLINE (⧺) #-} (⧺) = append -- | /O(1)/ Extract the first bit of a non-empty 'Bitstream'. An -- exception will be thrown if empty. head ∷ Bitstream α ⇒ α → Bool {-# RULES "Bitstream head/unstream fusion" ∀s. head (unstream s) = S.head s #-} {-# INLINE [0] head #-} head = S.head ∘ stream -- | /strict: O(1), lazy: O(n)/ Extract the last bit of a finite -- 'Bitstream'. An exception will be thrown if empty. last ∷ Bitstream α ⇒ α → Bool {-# RULES "Bitstream last/unstream fusion" ∀s. last (unstream s) = S.last s #-} {-# INLINE [0] last #-} last = S.last ∘ stream -- | /O(1)/ Extract the bits after the 'head' of a non-empty -- 'Bitstream'. An exception will be thrown if empty. tail ∷ Bitstream α ⇒ α → α {-# RULES "Bitstream tail/unstream fusion" ∀s. tail (unstream s) = unstream (S.tail s) #-} {-# INLINE [0] tail #-} tail = basicTail -- | /O(n)/ Return all the bits of a 'Bitstream' except the last -- one. An exception will be thrown if empty. init ∷ Bitstream α ⇒ α → α {-# RULES "Bitstream init/unstream fusion" ∀s. init (unstream s) = unstream (S.init s) #-} {-# INLINE [0] init #-} init = basicInit -- | /O(1)/ Test whether a 'Bitstream' is empty. null ∷ Bitstream α ⇒ α → Bool {-# RULES "Bitstream null/unstream fusion" ∀s. null (unstream s) = S.null s #-} {-# INLINE [0] null #-} null = S.null ∘ stream -- | /strict: O(1), lazy: O(n)/ Return the length of a finite -- 'Bitstream'. length ∷ Bitstream α ⇒ Num n ⇒ α → n {-# RULES "Bitstream length/unstream fusion" ∀s. length (unstream s) = genericLength s #-} {-# INLINE [0] length #-} length = genericLength ∘ stream -- | /O(n)/ Map a function over a 'Bitstream'. map ∷ Bitstream α ⇒ (Bool → Bool) → α → α {-# RULES "Bitstream map/unstream fusion" ∀f s. map f (unstream s) = unstream (S.map f s) #-} {-# INLINE [0] map #-} map = basicMap -- | /O(n)/ Reverse a 'Bitstream'. reverse ∷ Bitstream α ⇒ α → α {-# INLINE [0] reverse #-} reverse = basicReverse -- | /O(n)/ 'foldl', applied to a binary operator, a starting value -- (typically the left-identity of the operator), and a 'Bitstream', -- reduces the 'Bitstream' using the binary operator, from left to -- right: -- -- @ -- 'foldl' f z [x1, x2, ..., xn] == (...((z `f` x1) `f` x2) `f`...) `f` xn -- @ -- -- The 'Bitstream' must be finite. foldl ∷ Bitstream α ⇒ (β → Bool → β) → β → α → β {-# RULES "Bitstream foldl/unstream fusion" ∀f β s. foldl f β (unstream s) = S.foldl f β s #-} {-# INLINE [0] foldl #-} foldl f β = S.foldl f β ∘ stream -- | /O(n)/ 'foldl'' is a variant of 'foldl' that is strict on the -- accumulator. foldl' ∷ Bitstream α ⇒ (β → Bool → β) → β → α → β {-# RULES "Bitstream foldl'/unstream fusion" ∀f β s. foldl' f β (unstream s) = S.foldl' f β s #-} {-# INLINE [0] foldl' #-} foldl' f β = S.foldl' f β ∘ stream -- | /O(n)/ 'foldl1' is a variant of 'foldl' that has no starting -- value argument, and thus must be applied to non-empty 'Bitstream's. foldl1 ∷ Bitstream α ⇒ (Bool → Bool → Bool) → α → Bool {-# RULES "Bitstream foldl1/unstream fusion" ∀f s. foldl1 f (unstream s) = S.foldl1 f s #-} {-# INLINE [0] foldl1 #-} foldl1 f = S.foldl1 f ∘ stream -- | /O(n)/ A strict version of 'foldl1'. foldl1' ∷ Bitstream α ⇒ (Bool → Bool → Bool) → α → Bool {-# RULES "Bitstream foldl1'/unstream fusion" ∀f s. foldl1' f (unstream s) = S.foldl1' f s #-} {-# INLINE [0] foldl1' #-} foldl1' f = S.foldl1' f ∘ stream -- | /O(n)/ 'foldr', applied to a binary operator, a starting value -- (typically the right-identity of the operator), and a 'Bitstream', -- reduces the 'Bitstream' using the binary operator, from right to -- left: -- -- @ -- 'foldr' f z [x1, x2, ..., xn] == x1 `f` (x2 `f` ... (xn `f` z)...) -- @ foldr ∷ Bitstream α ⇒ (Bool → β → β) → β → α → β {-# RULES "Bitstream foldr/unstream fusion" ∀f β s. foldr f β (unstream s) = S.foldr f β s #-} {-# INLINE [0] foldr #-} foldr f β = S.foldr f β ∘ stream -- | /O(n)/ 'foldr1' is a variant of 'foldr' that has no starting -- value argument, and thus must be applied to non-empty 'Bitstream's. foldr1 ∷ Bitstream α ⇒ (Bool → Bool → Bool) → α → Bool {-# RULES "Bitstream foldr1/unstream fusion" ∀f s. foldr1 f (unstream s) = S.foldr1 f s #-} {-# INLINE [0] foldr1 #-} foldr1 f = S.foldr1 f ∘ stream -- | /O(n)/ Concatenate all 'Bitstream's in the list. concat ∷ Bitstream α ⇒ [α] → α {-# INLINE [0] concat #-} concat = basicConcat -- | Map a function over a 'Bitstream' and concatenate the results. concatMap ∷ Bitstream α ⇒ (Bool → α) → α → α {-# RULES "Bitstream concatMap/unstream fusion" ∀f s. concatMap f (unstream s) = unstream (S.concatMap f s) #-} {-# INLINE [0] concatMap #-} concatMap f = concat ∘ L.map f ∘ unpack -- | /O(n)/ 'and' returns the conjunction of a 'Bool' list. For the -- result to be 'True', the 'Bitstream' must be finite; 'False', -- however, results from a 'False' value at a finite index of a finite -- or infinite 'Bitstream'. Note that strict 'Bitstream's are always -- finite. and ∷ Bitstream α ⇒ α → Bool {-# RULES "Bitstream and/unstream fusion" ∀s. and (unstream s) = S.and s #-} {-# INLINE [0] and #-} and = S.and ∘ stream -- | /O(n)/ 'or' returns the disjunction of a 'Bool' list. For the -- result to be 'False', the 'Bitstream' must be finite; 'True', -- however, results from a 'True' value at a finite index of a finite -- or infinite 'Bitstream'. Note that strict 'Bitstream's are always -- finite. or ∷ Bitstream α ⇒ α → Bool {-# RULES "Bitstream or/unstream fusion" ∀s. or (unstream s) = S.or s #-} {-# INLINE [0] or #-} or = S.or ∘ stream -- | /O(n)/ Applied to a predicate and a 'Bitstream', 'any' determines -- if any bit of the 'Bitstream' satisfies the predicate. For the -- result to be 'False', the 'Bitstream' must be finite; 'True', -- however, results from a 'True' value for the predicate applied to a -- bit at a finite index of a finite or infinite 'Bitstream'. any ∷ Bitstream α ⇒ (Bool → Bool) → α → Bool {-# RULES "Bitstream any/unstream fusion" ∀f s. any f (unstream s) = S.or (S.map f s) #-} {-# INLINE [0] any #-} any f = S.or ∘ S.map f ∘ stream -- | /O(n)/ Applied to a predicate and a 'Bitstream', 'all' determines -- if all bits of the 'Bitstream' satisfy the predicate. For the -- result to be 'True', the 'Bitstream' must be finite; 'False', -- however, results from a 'False' value for the predicate applied to -- a bit at a finite index of a finite or infinite 'Bitstream'. all ∷ Bitstream α ⇒ (Bool → Bool) → α → Bool {-# RULES "Bitstream all/unstream fusion" ∀f s. all f (unstream s) = S.and (S.map f s) #-} {-# INLINE [0] all #-} all f = S.and ∘ S.map f ∘ stream -- | /O(n)/ 'scanl' is similar to 'foldl', but returns a 'Bitstream' -- of successive reduced bits from the left: -- -- @ -- 'scanl' f z [x1, x2, ...] == [z, z `f` x1, (z `f` x1) `f` x2, ...] -- @ -- -- Note that -- -- @ -- 'last' ('scanl' f z xs) == 'foldl' f z xs -- @ scanl ∷ Bitstream α ⇒ (Bool → Bool → Bool) → Bool → α → α {-# RULES "Bitstream scanl/unstream fusion" ∀f b s. scanl f b (unstream s) = unstream (S.scanl f b s) #-} {-# INLINE [0] scanl #-} scanl = basicScanl -- | /O(n)/ 'scanl1' is a variant of 'scanl' that has no starting -- value argument: -- -- @ -- 'scanl1' f [x1, x2, ...] == [x1, x1 `f` x2, ...] -- @ scanl1 ∷ Bitstream α ⇒ (Bool → Bool → Bool) → α → α {-# INLINE [0] scanl1 #-} scanl1 f α | null α = α | otherwise = scanl f (head α) (tail α) -- | /O(n)/ 'scanr' is the right-to-left dual of 'scanl'. Note that -- -- @ -- 'head' ('scanr' f z xs) == 'foldr' f z xs -- @ scanr ∷ Bitstream α ⇒ (Bool → Bool → Bool) → Bool → α → α {-# INLINE [0] scanr #-} scanr f b = reverse ∘ scanl (flip f) b ∘ reverse -- | /O(n)/ 'scanr1' is a variant of 'scanr' that has no starting -- value argument. scanr1 ∷ Bitstream α ⇒ (Bool → Bool → Bool) → α → α {-# INLINE [0] scanr1 #-} scanr1 f = reverse ∘ scanl1 (flip f) ∘ reverse -- | /O(n)/ @'replicate' n x@ is a 'Bitstream' of length @n@ with @x@ -- the value of every bit. replicate ∷ (Integral n, Bitstream α) ⇒ n → Bool → α {-# INLINE replicate #-} replicate n = unstream ∘ genericReplicate n -- | /O(n)/ The 'unfoldr' function is a \`dual\' to 'foldr': while -- 'foldr' reduces a 'Bitstream' to a summary value, 'unfoldr' builds -- a 'Bitstream' from a seed value. The function takes the element and -- returns 'Nothing' if it is done producing the 'Bitstream' or -- returns 'Just' @(a, b)@, in which case, @a@ is a prepended to the -- 'Bitstream' and @b@ is used as the next element in a recursive -- call. unfoldr ∷ Bitstream α ⇒ (β → Maybe (Bool, β)) → β → α {-# INLINE unfoldr #-} unfoldr f = unstream ∘ S.unfoldr f -- | /O(n)/ 'unfoldrN' is a variant of 'unfoldr' but constructs a -- 'Bitstream' with at most @n@ bits. unfoldrN ∷ (Integral n, Bitstream α) ⇒ n → (β → Maybe (Bool, β)) → β → α {-# INLINE unfoldrN #-} unfoldrN n f = unstream ∘ genericUnfoldrN n f -- | /O(n)/ 'take' @n@, applied to a 'Bitstream' @xs@, returns the -- prefix of @xs@ of length @n@, or @xs@ itself if @n > 'length' xs@. take ∷ (Integral n, Bitstream α) ⇒ n → α → α {-# RULES "Bitstream take/unstream fusion" ∀n s. take n (unstream s) = unstream (genericTake n s) #-} {-# INLINE [0] take #-} take = basicTake -- | /O(n)/ 'drop' @n xs@ returns the suffix of @xs@ after the first -- @n@ bits, or 'empty' if @n > 'length' xs@. drop ∷ (Integral n, Bitstream α) ⇒ n → α → α {-# RULES "Bitstream drop/unstream fusion" ∀n s. drop n (unstream s) = unstream (genericDrop n s) #-} {-# INLINE [0] drop #-} drop = basicDrop -- | /O(n)/ 'takeWhile', applied to a predicate @p@ and a 'Bitstream' -- @xs@, returns the longest prefix (possibly 'empty') of @xs@ of bits -- that satisfy @p@. takeWhile ∷ Bitstream α ⇒ (Bool → Bool) → α → α {-# RULES "Bitstream takeWhile/unstream fusion" ∀f s. takeWhile f (unstream s) = unstream (S.takeWhile f s) #-} {-# INLINE [0] takeWhile #-} takeWhile = basicTakeWhile -- | /O(n)/ 'dropWhile' @p xs@ returns the suffix remaining after -- 'takeWhile' @p xs@. dropWhile ∷ Bitstream α ⇒ (Bool → Bool) → α → α {-# RULES "Bitstream dropWhile/unstream fusion" ∀f s. dropWhile f (unstream s) = unstream (S.dropWhile f s) #-} {-# INLINE [0] dropWhile #-} dropWhile = basicDropWhile -- | /O(n)/ 'span', applied to a predicate @p@ and a 'Bitstream' @xs@, -- returns a tuple where first element is longest prefix (possibly -- 'empty') of @xs@ of bits that satisfy @p@ and second element is the -- remainder of the 'Bitstream'. -- -- 'span' @p xs@ is equivalent to @('takeWhile' p xs, 'dropWhile' p -- xs)@ span ∷ Bitstream α ⇒ (Bool → Bool) → α → (α, α) {-# INLINE span #-} span f α = let hd = takeWhile f α tl = drop (length hd ∷ Integer) α in (hd, tl) -- | /O(n)/ 'break', applied to a predicate @p@ and a 'Bitstream' -- @xs@, returns a tuple where first element is longest prefix -- (possibly 'empty') of @xs@ of bits that /do not satisfy/ @p@ and -- second element is the remainder of the 'Bitstream'. -- -- 'break' @p@ is equivalent to @'span' ('not' . p)@. break ∷ Bitstream α ⇒ (Bool → Bool) → α → (α, α) {-# INLINE break #-} break f = span ((¬) ∘ f) -- | /O(n)/ 'elem' is the 'Bitstream' membership predicate, usually -- written in infix form, e.g., @x \`elem\` xs@. For the result to be -- 'False', the 'Bitstream' must be finite; 'True', however, results -- from an bit equal to @x@ found at a finite index of a finite or -- infinite 'Bitstream'. elem ∷ Bitstream α ⇒ Bool → α → Bool {-# RULES "Bitstream elem/unstream fusion" ∀b s. elem b (unstream s) = S.elem b s #-} {-# INLINE [0] elem #-} elem True = or elem False = (¬) ∘ and -- | (∈) = 'elem' -- -- U+2208, ELEMENT OF (∈) ∷ Bitstream α ⇒ Bool → α → Bool {-# INLINE (∈) #-} (∈) = elem -- | (∋) = 'flip' (∈) -- -- U+220B, CONTAINS AS MEMBER (∋) ∷ Bitstream α ⇒ α → Bool → Bool {-# INLINE (∋) #-} (∋) = flip elem -- | /O(n)/ 'notElem' is the negation of 'elem'. notElem ∷ Bitstream α ⇒ Bool → α → Bool {-# RULES "Bitstream notElem/unstream fusion" ∀b s. notElem b (unstream s) = S.notElem b s #-} {-# INLINE [0] notElem #-} notElem = ((¬) ∘) ∘ (∈) -- | (∉) = 'notElem' -- -- U+2209, NOT AN ELEMENT OF (∉) ∷ Bitstream α ⇒ Bool → α → Bool {-# INLINE (∉) #-} (∉) = notElem -- | (∌) = 'flip' (∉) -- -- U+220C, DOES NOT CONTAIN AS MEMBER (∌) ∷ Bitstream α ⇒ α → Bool → Bool (∌) = flip notElem {-# INLINE (∌) #-} -- | /O(n)/ The 'find' function takes a predicate and a 'Bitstream' -- and returns the bit in the 'Bitstream' matching the predicate, or -- 'Nothing' if there is no such bit. find ∷ Bitstream α ⇒ (Bool → Bool) → α → Maybe Bool {-# RULES "Bitstream find/unstream fusion" ∀f s. find f (unstream s) = S.find f s #-} {-# INLINE [0] find #-} find f = S.find f ∘ stream -- | /O(n)/ 'filter', applied to a predicate and a 'Bitstream', -- returns the 'Bitstream' of those bits that satisfy the predicate. filter ∷ Bitstream α ⇒ (Bool → Bool) → α → α {-# RULES "Bitstream filter/unstream fusion" ∀f s. filter f (unstream s) = unstream (S.filter f s) #-} {-# INLINE [0] filter #-} filter = basicFilter -- | /O(n)/ The 'partition' function takes a predicate and a -- 'Bitstream' and returns the pair of 'Bitstream's of bits which do -- and do not satisfy the predicate, respectively. partition ∷ Bitstream α ⇒ (Bool → Bool) → α → (α, α) {-# INLINE [0] partition #-} partition = basicPartition -- | /O(n)/ 'Bitstream' index (subscript) operator, starting from 0. (!!) ∷ (Bitstream α, Integral n, Show n) ⇒ α → n → Bool {-# RULES "Bitstream (!!)/unstream fusion" ∀s n. (unstream s) !! n = genericIndex s n #-} {-# INLINE [0] (!!) #-} α !! n = genericIndex (stream α) n -- | /O(n)/ The 'elemIndex' function returns the index of the first -- bit in the given 'Bitstream' which is equal to the query bit, or -- 'Nothing' if there is no such bit. elemIndex ∷ (Bitstream α, Integral n) ⇒ Bool → α → Maybe n {-# RULES "Bitstream elemIndex/unstream fusion" ∀b s. elemIndex b (unstream s) = genericFindIndex (≡ b) s #-} {-# INLINE [0] elemIndex #-} elemIndex = findIndex ∘ (≡) -- | /O(n)/ The 'elemIndices' function extends 'elemIndex', by -- returning the indices of all bits equal to the query bit, in -- ascending order. elemIndices ∷ (Bitstream α, Integral n) ⇒ Bool → α → [n] {-# RULES "Bitstream elemIndices/unstream fusion" ∀b s. elemIndices b (unstream s) = S.toList $ S.map fst $ S.filter ((≡ b) ∘ snd) $ genericIndexed s #-} {-# INLINE [0] elemIndices #-} elemIndices = findIndices ∘ (≡) -- | /O(n)/ The 'findIndex' function takes a predicate and a -- 'Bitstream' and returns the index of the first bit in the -- 'Bitstream' satisfying the predicate, or 'Nothing' if there is no -- such bit. findIndex ∷ (Bitstream α, Integral n) ⇒ (Bool → Bool) → α → Maybe n {-# RULES "Bitstream findIndex/unstream fusion" ∀f s. findIndex f (unstream s) = genericFindIndex f s #-} {-# INLINE [0] findIndex #-} findIndex f = genericFindIndex f ∘ stream -- | /O(n)/ The 'findIndices' function extends 'findIndex', by -- returning the indices of all bits satisfying the predicate, in -- ascending order. findIndices ∷ (Bitstream α, Integral n) ⇒ (Bool → Bool) → α → [n] {-# RULES "Bitstream findIndices/unstream fusion" ∀f s. findIndices f (unstream s) = S.toList $ S.map fst $ S.filter (f ∘ snd) $ genericIndexed s #-} {-# INLINE [0] findIndices #-} findIndices f = S.toList ∘ S.map fst ∘ S.filter (f ∘ snd) ∘ genericIndexed ∘ stream -- | /O(min(m, n))/ 'zip' takes two 'Bitstream's and returns a list of -- corresponding bit pairs. If one input 'Bitstream' is short, excess -- bits of the longer 'Bitstream' are discarded. zip ∷ Bitstream α ⇒ α → α → [(Bool, Bool)] {-# RULES "Bitstream zip/unstream fusion" ∀s1 s2. zip (unstream s1) (unstream s2) = S.toList (S.zip s1 s2) #-} {-# INLINE [0] zip #-} zip = zipWith (,) -- | The 'zip3' function takes three 'Bitstream's and returns a list -- of triples, analogous to 'zip'. zip3 ∷ Bitstream α ⇒ α → α → α → [(Bool, Bool, Bool)] {-# RULES "Bitstream zip3/unstream fusion" ∀s1 s2 s3. zip3 (unstream s1) (unstream s2) (unstream s3) = S.toList (S.zip3 s1 s2 s3) #-} {-# INLINE [0] zip3 #-} zip3 = zipWith3 (,,) -- | The 'zip4' function takes four lists and returns a list of -- quadruples, analogous to 'zip'. zip4 ∷ Bitstream α ⇒ α → α → α → α → [(Bool, Bool, Bool, Bool)] {-# RULES "Bitstream zip4/unstream fusion" ∀s1 s2 s3 s4. zip4 (unstream s1) (unstream s2) (unstream s3) (unstream s4) = S.toList (S.zip4 s1 s2 s3 s4) #-} {-# INLINE [0] zip4 #-} zip4 = zipWith4 (,,,) -- | The 'zip5' function takes five 'Bitstream's and returns a list of -- five-tuples, analogous to 'zip'. zip5 ∷ Bitstream α ⇒ α → α → α → α → α → [(Bool, Bool, Bool, Bool, Bool)] {-# RULES "Bitstream zip5/unstream fusion" ∀s1 s2 s3 s4 s5. zip5 (unstream s1) (unstream s2) (unstream s3) (unstream s4) (unstream s5) = S.toList (S.zip5 s1 s2 s3 s4 s5) #-} {-# INLINE [0] zip5 #-} zip5 = zipWith5 (,,,,) -- | The 'zip6' function takes six 'Bitstream's and returns a list of -- six-tuples, analogous to 'zip'. zip6 ∷ Bitstream α ⇒ α → α → α → α → α → α → [(Bool, Bool, Bool, Bool, Bool, Bool)] {-# RULES "Bitstream zip6/unstream fusion" ∀s1 s2 s3 s4 s5 s6. zip6 (unstream s1) (unstream s2) (unstream s3) (unstream s4) (unstream s5) (unstream s6) = S.toList (S.zip6 s1 s2 s3 s4 s5 s6) #-} {-# INLINE [0] zip6 #-} zip6 = zipWith6 (,,,,,) -- | /O(min(m, n))/ 'zipWith' generalises 'zip' by zipping with the -- function given as the first argument, instead of a tupling -- function. zipWith ∷ Bitstream α ⇒ (Bool → Bool → β) → α → α → [β] {-# RULES "Bitstream zipWith/unstream fusion" ∀f s1 s2. zipWith f (unstream s1) (unstream s2) = S.toList (S.zipWith f s1 s2) #-} {-# INLINEABLE [0] zipWith #-} zipWith f α β = S.toList $ S.zipWith f (stream α) (stream β) -- | The 'zipWith3' function takes a function which combines three -- bits, as well as three 'Bitstream's and returns a list of their -- point-wise combination, analogous to 'zipWith'. zipWith3 ∷ Bitstream α ⇒ (Bool → Bool → Bool → β) → α → α → α → [β] {-# RULES "Bitstream zipWith3/unstream fusion" ∀f s1 s2 s3. zipWith3 f (unstream s1) (unstream s2) (unstream s3) = S.toList (S.zipWith3 f s1 s2 s3) #-} {-# INLINEABLE [0] zipWith3 #-} zipWith3 f α β γ = S.toList $ S.zipWith3 f (stream α) (stream β) (stream γ) -- | The 'zipWith4' function takes a function which combines four -- bits, as well as four 'Bitstream's and returns a list of their -- point-wise combination, analogous to 'zipWith'. zipWith4 ∷ Bitstream α ⇒ (Bool → Bool → Bool → Bool → β) → α → α → α → α → [β] {-# RULES "Bitstream zipWith4/unstream fusion" ∀f s1 s2 s3 s4. zipWith4 f (unstream s1) (unstream s2) (unstream s3) (unstream s4) = S.toList (S.zipWith4 f s1 s2 s3 s4) #-} {-# INLINEABLE [0] zipWith4 #-} zipWith4 f α β γ δ = S.toList $ S.zipWith4 f (stream α) (stream β) (stream γ) (stream δ) -- | The 'zipWith5' function takes a function which combines five -- bits, as well as five 'Bitstream's and returns a list of their -- point-wise combination, analogous to 'zipWith'. zipWith5 ∷ Bitstream α ⇒ (Bool → Bool → Bool → Bool → Bool → β) → α → α → α → α → α → [β] {-# RULES "Bitstream zipWith5/unstream fusion" ∀f s1 s2 s3 s4 s5. zipWith5 f (unstream s1) (unstream s2) (unstream s3) (unstream s4) (unstream s5) = S.toList (S.zipWith5 f s1 s2 s3 s4 s5) #-} {-# INLINEABLE [0] zipWith5 #-} zipWith5 f α β γ δ ε = S.toList $ S.zipWith5 f (stream α) (stream β) (stream γ) (stream δ) (stream ε) -- | The 'zipWith6' function takes a function which combines six bits, -- as well as six 'Bitstream's and returns a list of their point-wise -- combination, analogous to 'zipWith'. zipWith6 ∷ Bitstream α ⇒ (Bool → Bool → Bool → Bool → Bool → Bool → β) → α → α → α → α → α → α → [β] {-# RULES "Bitstream zipWith6/unstream fusion" ∀f s1 s2 s3 s4 s5 s6. zipWith6 f (unstream s1) (unstream s2) (unstream s3) (unstream s4) (unstream s5) (unstream s6) = S.toList (S.zipWith6 f s1 s2 s3 s4 s5 s6) #-} {-# INLINEABLE [0] zipWith6 #-} zipWith6 f α β γ δ ε ζ = S.toList $ S.zipWith6 f (stream α) (stream β) (stream γ) (stream δ) (stream ε) (stream ζ) -- | /O(min(m, n))/ 'unzip' transforms a list of bit pairs into a -- 'Bitstream' of first components and a 'Bitstream' of second -- components. unzip ∷ Bitstream α ⇒ [(Bool, Bool)] → (α, α) {-# INLINEABLE unzip #-} unzip xs = ( unstream $ S.map fst $ S.fromList xs , unstream $ S.map snd $ S.fromList xs ) -- | The 'unzip3' function takes a list of triples and returns three -- 'Bitstream's, analogous to 'unzip'. unzip3 ∷ Bitstream α ⇒ [(Bool, Bool, Bool)] → (α, α, α) {-# INLINEABLE unzip3 #-} unzip3 xs = ( unstream $ S.map (\(α, _, _) → α) $ S.fromList xs , unstream $ S.map (\(_, β, _) → β) $ S.fromList xs , unstream $ S.map (\(_, _, γ) → γ) $ S.fromList xs ) -- | The 'unzip4' function takes a list of quadruples and returns -- four 'Bitstream's, analogous to 'unzip'. unzip4 ∷ Bitstream α ⇒ [(Bool, Bool, Bool, Bool)] → (α, α, α, α) {-# INLINEABLE unzip4 #-} unzip4 xs = ( unstream $ S.map (\(α, _, _, _) → α) $ S.fromList xs , unstream $ S.map (\(_, β, _, _) → β) $ S.fromList xs , unstream $ S.map (\(_, _, γ, _) → γ) $ S.fromList xs , unstream $ S.map (\(_, _, _, δ) → δ) $ S.fromList xs ) -- | The 'unzip5' function takes a list of five-tuples and returns -- five 'Bitstream's, analogous to 'unzip'. unzip5 ∷ Bitstream α ⇒ [(Bool, Bool, Bool, Bool, Bool)] → (α, α, α, α, α) {-# INLINEABLE unzip5 #-} unzip5 xs = ( unstream $ S.map (\(α, _, _, _, _) → α) $ S.fromList xs , unstream $ S.map (\(_, β, _, _, _) → β) $ S.fromList xs , unstream $ S.map (\(_, _, γ, _, _) → γ) $ S.fromList xs , unstream $ S.map (\(_, _, _, δ, _) → δ) $ S.fromList xs , unstream $ S.map (\(_, _, _, _, ε) → ε) $ S.fromList xs ) -- | The 'unzip6' function takes a list of six-tuples and returns six -- 'Bitstream's, analogous to 'unzip'. unzip6 ∷ Bitstream α ⇒ [(Bool, Bool, Bool, Bool, Bool, Bool)] → (α, α, α, α, α, α) {-# INLINEABLE unzip6 #-} unzip6 xs = ( unstream $ S.map (\(α, _, _, _, _, _) → α) $ S.fromList xs , unstream $ S.map (\(_, β, _, _, _, _) → β) $ S.fromList xs , unstream $ S.map (\(_, _, γ, _, _, _) → γ) $ S.fromList xs , unstream $ S.map (\(_, _, _, δ, _, _) → δ) $ S.fromList xs , unstream $ S.map (\(_, _, _, _, ε, _) → ε) $ S.fromList xs , unstream $ S.map (\(_, _, _, _, _, ζ) → ζ) $ S.fromList xs )