strict-list 0.1.7.6 → 1.0.0.1
raw patch · 6 files changed
Files
- CHANGELOG.md +10/−0
- README.md +17/−0
- library/StrictList.hs +160/−63
- library/StrictList/Prelude.hs +0/−85
- strict-list.cabal +12/−9
- test/Main.hs +10/−7
+ CHANGELOG.md view
@@ -0,0 +1,10 @@+## v1.0.0.0++### Breaking changes++- The strict list type was renamed from `List` to `StrictList` throughout the API to avoid confusion with the `List` type alias from `base`.+- `Ord` for `StrictList` now matches the standard lazy list ordering, so empty lists compare smaller than non-empty lists and lexicographic comparison follows the usual `[]` semantics.++### Other changes++- Added `Arbitrary` support in the test suite.
+ README.md view
@@ -0,0 +1,17 @@+# strict-list++[](https://hackage.haskell.org/package/strict-list)+[](https://nikita-volkov.github.io/strict-list/)++Strict linked lists for Haskell, with stack-safe operations and reversed-order helpers for efficient intermediate work.++## Example++```haskell+{-# LANGUAGE OverloadedLists #-}++import StrictList++numbers :: StrictList Int+numbers = [1, 2, 3]+```
library/StrictList.hs view
@@ -2,8 +2,8 @@ -- Definitions of strict linked list. -- -- Most basic operations like `fmap`, `filter`, `<*>`--- can only be implemented efficiently by producing an intermediate list in reversed order--- and then reversing it to the original order.+-- are implemented efficiently by producing an intermediate list in reversed order+-- and then reversing it to the original order to avoid stack recursion. -- These intermediate reversed functions are exposed by the API, -- because they very well may be useful for efficient implementations of data-structures built on top of list. -- E.g., the <http://hackage.haskell.org/package/deque "deque"> package exploits them heavily.@@ -12,37 +12,116 @@ -- whenever you see that a function has a reversed counterpart, -- that counterpart is faster and hence if you don't care about the order or -- intend to reverse the list further down the line, you should give preference to that counterpart.------ The typical `toList` and `fromList` conversions are provided by means of--- the `Foldable` and `IsList` instances.-module StrictList where+module StrictList+ ( -- * Strict list type+ StrictList (Cons, Nil), -import StrictList.Prelude hiding (drop, dropWhile, reverse, take, takeWhile)+ -- * Conversions+ toList,+ toListReversed,+ fromList,+ fromListReversed, + -- * Basic transformations+ reverse,+ take,+ takeReversed,+ drop,+ filter,+ filterReversed,+ takeWhile,+ takeWhileReversed,+ dropWhile,+ takeWhileFromEnding,+ dropWhileFromEnding,+ span,+ spanReversed,+ spanFromEnding,+ break,+ breakReversed,++ -- * Queries+ match,+ uncons,+ head,+ last,+ tail,+ init,+ initReversed,++ -- * Zipping and application+ apZipping,+ apZippingReversed,++ -- * Reversed-order helpers+ prependReversed,+ mapReversed,+ apReversed,+ explodeReversed,+ joinReversed,+ mapMaybeReversed,+ catMaybesReversed,+ )+where++import Control.Applicative (Alternative (..), Applicative (..))+import Control.Arrow (first)+import Control.DeepSeq (NFData, NFData1)+import Control.Monad (Monad (..), MonadPlus (..))+import Data.Bool (Bool (..))+import Data.Data (Data)+import Data.Foldable (Foldable (foldl', foldr))+import Data.Function (const, flip, id, (.))+import Data.Functor (Functor (..), (<$>))+import Data.Functor.Alt (Alt (..))+import Data.Functor.Apply (Apply ((<.>)))+import Data.Functor.Bind (Bind (..))+import Data.Functor.Plus (Plus (..))+import Data.Hashable (Hashable)+import Data.Int (Int)+import Data.Maybe (Maybe (..))+import Data.Monoid (Monoid (..))+import Data.Ord (Ord (..), Ordering (..))+import Data.Semigroup (Semigroup (..))+import Data.Traversable (Traversable (sequenceA))+import qualified GHC.Exts+import GHC.Generics (Generic, Generic1)+import qualified Test.QuickCheck as Qc+import Prelude (Eq (..), Read, Show, pred)+ -- | -- Strict linked list.-data List a = Cons !a !(List a) | Nil+data StrictList a = Cons !a !(StrictList a) | Nil deriving- (Eq, Ord, Show, Read, Generic, Generic1, Data, Typeable)+ (Eq, Show, Read, Generic, Generic1, Data) -instance IsList (List a) where- type Item (List a) = a- fromList = reverse . fromListReversed- toList = foldr (:) []+instance (Ord a) => Ord (StrictList a) where+ compare Nil Nil = EQ+ compare Nil _ = LT+ compare _ Nil = GT+ compare (Cons leftHead leftTail) (Cons rightHead rightTail) =+ case compare leftHead rightHead of+ EQ -> compare leftTail rightTail+ ordering -> ordering -instance Semigroup (List a) where+instance GHC.Exts.IsList (StrictList a) where+ type Item (StrictList a) = a+ fromList = fromList+ toList = toList++instance Semigroup (StrictList a) where (<>) a b = case b of Nil -> a _ -> prependReversed (reverse a) b -instance Monoid (List a) where+instance Monoid (StrictList a) where mempty = Nil mappend = (<>) -instance Functor List where+instance Functor StrictList where fmap f = reverse . mapReversed f -instance Foldable List where+instance Foldable StrictList where foldr step init = let loop = \case Cons head tail -> step head (loop tail)@@ -54,47 +133,60 @@ _ -> acc in loop init -instance Traversable List where+instance Traversable StrictList where sequenceA = foldr (liftA2 Cons) (pure Nil) -instance Apply List where+instance Apply StrictList where (<.>) fList aList = apReversed (reverse fList) (reverse aList) -instance Applicative List where+instance Applicative StrictList where pure a = Cons a Nil (<*>) = (<.>) -instance Alt List where+instance Alt StrictList where (<!>) = mappend -instance Plus List where+instance Plus StrictList where zero = mempty -instance Alternative List where+instance Alternative StrictList where empty = zero (<|>) = (<!>) -instance Bind List where+instance Bind StrictList where (>>-) ma amb = reverse (explodeReversed amb ma) join = reverse . joinReversed -instance Monad List where+instance Monad StrictList where return = pure (>>=) = (>>-) -instance MonadPlus List where+instance MonadPlus StrictList where mzero = empty mplus = (<|>) -instance (Hashable a) => Hashable (List a)+instance (Hashable a) => Hashable (StrictList a) -instance (NFData a) => NFData (List a)+instance (NFData a) => NFData (StrictList a) -instance NFData1 List+instance NFData1 StrictList +instance (Qc.Arbitrary a) => Qc.Arbitrary (StrictList a) where+ arbitrary = fromList <$> Qc.arbitrary+ shrink = fmap fromList . Qc.shrink . toList++instance Qc.Arbitrary1 StrictList where+ liftArbitrary elemGen = fromList <$> Qc.liftArbitrary elemGen+ liftShrink elemShrink = fmap fromList . Qc.liftShrink elemShrink . toList+ -- |+-- Convert to lazy list.+toList :: StrictList a -> [a]+toList = foldr (:) []++-- | -- Convert to lazy list in normal form (with all elements and spine evaluated).-toListReversed :: List a -> [a]+toListReversed :: StrictList a -> [a] toListReversed = go [] where go !outputList = \case@@ -102,20 +194,25 @@ Nil -> outputList -- |+-- Construct from a lazy list.+fromList :: [a] -> StrictList a+fromList = reverse . fromListReversed++-- | -- Reverse the list. {-# INLINE reverse #-}-reverse :: List a -> List a+reverse :: StrictList a -> StrictList a reverse = foldl' (flip Cons) Nil -- | -- Leave only the specified amount of elements. {-# INLINE take #-}-take :: Int -> List a -> List a+take :: Int -> StrictList a -> StrictList a take amount = reverse . takeReversed amount -- | -- Leave only the specified amount of elements, in reverse order.-takeReversed :: Int -> List a -> List a+takeReversed :: Int -> StrictList a -> StrictList a takeReversed = let loop !output !amount = if amount > 0@@ -127,7 +224,7 @@ -- | -- Leave only the elements after the specified amount of first elements.-drop :: Int -> List a -> List a+drop :: Int -> StrictList a -> StrictList a drop amount = if amount > 0 then \case@@ -138,13 +235,13 @@ -- | -- Leave only the elements satisfying the predicate. {-# INLINE filter #-}-filter :: (a -> Bool) -> List a -> List a+filter :: (a -> Bool) -> StrictList a -> StrictList a filter predicate = reverse . filterReversed predicate -- | -- Leave only the elements satisfying the predicate, -- producing a list in reversed order.-filterReversed :: (a -> Bool) -> List a -> List a+filterReversed :: (a -> Bool) -> StrictList a -> StrictList a filterReversed predicate = let loop !newList = \case Cons head tail ->@@ -157,13 +254,13 @@ -- | -- Leave only the first elements satisfying the predicate. {-# INLINE takeWhile #-}-takeWhile :: (a -> Bool) -> List a -> List a+takeWhile :: (a -> Bool) -> StrictList a -> StrictList a takeWhile predicate = reverse . takeWhileReversed predicate -- | -- Leave only the first elements satisfying the predicate, -- producing a list in reversed order.-takeWhileReversed :: (a -> Bool) -> List a -> List a+takeWhileReversed :: (a -> Bool) -> StrictList a -> StrictList a takeWhileReversed predicate = let loop !newList = \case Cons head tail ->@@ -175,7 +272,7 @@ -- | -- Drop the first elements satisfying the predicate.-dropWhile :: (a -> Bool) -> List a -> List a+dropWhile :: (a -> Bool) -> StrictList a -> StrictList a dropWhile predicate = \case Cons head tail -> if predicate head@@ -189,12 +286,12 @@ -- -- >span predicate list = (takeWhile predicate list, dropWhile predicate list) {-# INLINE span #-}-span :: (a -> Bool) -> List a -> (List a, List a)+span :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a) span predicate = first reverse . spanReversed predicate -- | -- Same as `span`, only with the first list in reverse order.-spanReversed :: (a -> Bool) -> List a -> (List a, List a)+spanReversed :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a) spanReversed predicate = let buildPrefix !prefix = \case Cons head tail ->@@ -209,12 +306,12 @@ -- -- >break predicate = span (not . predicate) {-# INLINE break #-}-break :: (a -> Bool) -> List a -> (List a, List a)+break :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a) break predicate = first reverse . breakReversed predicate -- | -- Same as `break`, only with the first list in reverse order.-breakReversed :: (a -> Bool) -> List a -> (List a, List a)+breakReversed :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a) breakReversed predicate = let buildPrefix !prefix = \case Cons head tail ->@@ -231,7 +328,7 @@ -- >>> takeWhileFromEnding (> 2) (fromList [1,4,2,3,4,5]) -- fromList [5,4,3] {-# INLINE takeWhileFromEnding #-}-takeWhileFromEnding :: (a -> Bool) -> List a -> List a+takeWhileFromEnding :: (a -> Bool) -> StrictList a -> StrictList a takeWhileFromEnding predicate = foldl' ( \newList a ->@@ -247,7 +344,7 @@ -- -- >>> dropWhileFromEnding (> 2) (fromList [1,4,2,3,4,5]) -- fromList [2,4,1]-dropWhileFromEnding :: (a -> Bool) -> List a -> List a+dropWhileFromEnding :: (a -> Bool) -> StrictList a -> StrictList a dropWhileFromEnding predicate = let loop confirmed unconfirmed = \case Cons head tail ->@@ -261,7 +358,7 @@ -- | -- Same as @(`span` predicate . `reverse`)@.-spanFromEnding :: (a -> Bool) -> List a -> (List a, List a)+spanFromEnding :: (a -> Bool) -> StrictList a -> (StrictList a, StrictList a) spanFromEnding predicate = let loop !confirmedPrefix !unconfirmedPrefix !suffix = \case Cons head tail ->@@ -280,7 +377,7 @@ -- -- Essentially provides the same functionality as `either` for `Either` and `maybe` for `Maybe`. {-# INLINE match #-}-match :: result -> (element -> List element -> result) -> List element -> result+match :: result -> (element -> StrictList element -> result) -> StrictList element -> result match nil cons = \case Cons head tail -> cons head tail Nil -> nil@@ -288,7 +385,7 @@ -- | -- Get the first element and the remainder of the list if it's not empty. {-# INLINE uncons #-}-uncons :: List a -> Maybe (a, List a)+uncons :: StrictList a -> Maybe (a, StrictList a) uncons = \case Cons head tail -> Just (head, tail) _ -> Nothing@@ -296,7 +393,7 @@ -- | -- Get the first element, if list is not empty. {-# INLINE head #-}-head :: List a -> Maybe a+head :: StrictList a -> Maybe a head = \case Cons head _ -> Just head _ -> Nothing@@ -304,7 +401,7 @@ -- | -- Get the last element, if list is not empty. {-# INLINE last #-}-last :: List a -> Maybe a+last :: StrictList a -> Maybe a last = let loop !previous = \case Cons head tail -> loop (Just head) tail@@ -314,7 +411,7 @@ -- | -- Get all elements of the list but the first one. {-# INLINE tail #-}-tail :: List a -> List a+tail :: StrictList a -> StrictList a tail = \case Cons _ tail -> tail Nil -> Nil@@ -322,12 +419,12 @@ -- | -- Get all elements but the last one. {-# INLINE init #-}-init :: List a -> List a+init :: StrictList a -> StrictList a init = reverse . initReversed -- | -- Get all elements but the last one, producing the results in reverse order.-initReversed :: List a -> List a+initReversed :: StrictList a -> StrictList a initReversed = let loop !confirmed !unconfirmed = \case Cons head tail -> loop unconfirmed (Cons head unconfirmed) tail@@ -337,13 +434,13 @@ -- | -- Apply the functions in the left list to elements in the right one. {-# INLINE apZipping #-}-apZipping :: List (a -> b) -> List a -> List b+apZipping :: StrictList (a -> b) -> StrictList a -> StrictList b apZipping left right = apZippingReversed (reverse left) (reverse right) -- | -- Apply the functions in the left list to elements in the right one, -- producing a list of results in reversed order.-apZippingReversed :: List (a -> b) -> List a -> List b+apZippingReversed :: StrictList (a -> b) -> StrictList a -> StrictList b apZippingReversed = let loop bList = \case Cons f fTail -> \case@@ -359,21 +456,21 @@ -- | -- Construct from a lazy list in reversed order. {-# INLINE fromListReversed #-}-fromListReversed :: [a] -> List a+fromListReversed :: [a] -> StrictList a fromListReversed = foldl' (flip Cons) Nil -- | -- Add elements of the left list in reverse order -- in the beginning of the right list. {-# INLINE prependReversed #-}-prependReversed :: List a -> List a -> List a+prependReversed :: StrictList a -> StrictList a -> StrictList a prependReversed = \case Cons head tail -> prependReversed tail . Cons head Nil -> id -- | -- Map producing a list in reversed order.-mapReversed :: (a -> b) -> List a -> List b+mapReversed :: (a -> b) -> StrictList a -> StrictList b mapReversed f = let loop !newList = \case Cons head tail -> loop (Cons (f head) newList) tail@@ -384,26 +481,26 @@ -- Apply the functions in the left list to every element in the right one, -- producing a list of results in reversed order. {-# INLINE apReversed #-}-apReversed :: List (a -> b) -> List a -> List b+apReversed :: StrictList (a -> b) -> StrictList a -> StrictList b apReversed fList aList = foldl' (\z f -> foldl' (\z a -> Cons (f a) z) z aList) Nil fList -- | -- Use a function to produce a list of lists and then concat them sequentially, -- producing the results in reversed order. {-# INLINE explodeReversed #-}-explodeReversed :: (a -> List b) -> List a -> List b+explodeReversed :: (a -> StrictList b) -> StrictList a -> StrictList b explodeReversed amb = foldl' (\z -> foldl' (flip Cons) z . amb) Nil -- | -- Join (concat) producing results in reversed order. {-# INLINE joinReversed #-}-joinReversed :: List (List a) -> List a+joinReversed :: StrictList (StrictList a) -> StrictList a joinReversed = foldl' (foldl' (flip Cons)) Nil -- | -- Map and filter elements producing results in reversed order. {-# INLINE mapMaybeReversed #-}-mapMaybeReversed :: (a -> Maybe b) -> List a -> List b+mapMaybeReversed :: (a -> Maybe b) -> StrictList a -> StrictList b mapMaybeReversed f = go Nil where go !outputList = \case@@ -414,7 +511,7 @@ -- | -- Keep only the present values, reversing the order.-catMaybesReversed :: List (Maybe a) -> List a+catMaybesReversed :: StrictList (Maybe a) -> StrictList a catMaybesReversed = go Nil where go !outputList = \case
− library/StrictList/Prelude.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE CPP #-}--module StrictList.Prelude- ( module Exports,- )-where--import Control.Applicative as Exports-import Control.Arrow as Exports-import Control.Category as Exports-import Control.Concurrent as Exports-import Control.DeepSeq as Exports-import Control.Exception as Exports-import Control.Monad as Exports hiding (fail, forM, forM_, join, mapM, mapM_, msum, sequence, sequence_)-import Control.Monad.Fail as Exports-import Control.Monad.Fix as Exports hiding (fix)-import Control.Monad.IO.Class as Exports-import Control.Monad.ST as Exports-import Data.Bifunctor.Apply as Exports hiding (first, second)-import Data.Bits as Exports-import Data.Bool as Exports-import Data.Char as Exports-import Data.Coerce as Exports-import Data.Complex as Exports-import Data.Data as Exports-import Data.Dynamic as Exports-import Data.Either as Exports-import Data.Fixed as Exports-import Data.Foldable as Exports hiding (toList)-import Data.Function as Exports hiding (id, (.))-import Data.Functor as Exports-import Data.Functor.Alt as Exports hiding (many, optional, some, ($>))-import Data.Functor.Bind as Exports hiding (($>))-import Data.Functor.Extend as Exports-import Data.Functor.Identity as Exports-import Data.Functor.Plus as Exports hiding (many, optional, some, ($>))-import Data.Hashable as Exports (Hashable)-import Data.IORef as Exports-import Data.Int as Exports-import Data.Ix as Exports-#if MIN_VERSION_base(4,20,0)-import Data.List as Exports hiding (List, all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons, unzip)-#else-import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons, unzip)-#endif-import Data.Maybe as Exports-import Data.Monoid as Exports hiding (Alt, First (..), Last (..), (<>))-import Data.Ord as Exports-import Data.Proxy as Exports-import Data.Ratio as Exports-import Data.STRef as Exports-import Data.Semigroup as Exports-import Data.Semigroup.Bifoldable as Exports-import Data.Semigroup.Bitraversable as Exports-import Data.Semigroup.Foldable as Exports-import Data.Semigroup.Traversable as Exports-import Data.Semigroupoid as Exports-import Data.String as Exports-import Data.Traversable as Exports-import Data.Tuple as Exports-import Data.Unique as Exports-import Data.Version as Exports-import Data.Word as Exports-import Debug.Trace as Exports-import Foreign.ForeignPtr as Exports-import Foreign.Ptr as Exports-import Foreign.StablePtr as Exports-import Foreign.Storable as Exports hiding (alignment, sizeOf)-import GHC.Conc as Exports hiding (threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)-import GHC.Exts as Exports (IsList (..), groupWith, inline, lazy, sortWith)-import GHC.Generics as Exports (Generic, Generic1)-import GHC.IO.Exception as Exports-import Numeric as Exports-import System.Environment as Exports-import System.Exit as Exports-import System.IO as Exports-import System.IO.Error as Exports-import System.IO.Unsafe as Exports-import System.Mem as Exports-import System.Mem.StableName as Exports-import System.Timeout as Exports-import Text.Printf as Exports (hPrintf, printf)-import Text.Read as Exports (Read (..), readEither, readMaybe)-import Unsafe.Coerce as Exports-import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, fail, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, unzip, (.))
strict-list.cabal view
@@ -1,9 +1,9 @@ cabal-version: 3.0 name: strict-list-version: 0.1.7.6-synopsis: Strict linked list+version: 1.0.0.1+synopsis: Strict linked list with stack-safe operations description:- Implementation of strict linked list with care taken about stack.+ A strict linked list with stack-safe operations and reversed-order helpers. category: Data homepage: https://github.com/nikita-volkov/strict-list@@ -13,10 +13,13 @@ copyright: (c) 2019, Nikita Volkov license: MIT license-file: LICENSE+extra-doc-files:+ CHANGELOG.md+ README.md source-repository head type: git- location: git://github.com/nikita-volkov/strict-list.git+ location: https://github.com/nikita-volkov/strict-list common language-settings default-language: Haskell2010@@ -40,9 +43,9 @@ import: language-settings hs-source-dirs: library exposed-modules: StrictList- other-modules: StrictList.Prelude build-depends:- base >=4.9 && <5,+ QuickCheck >=2.14 && <3,+ base >=4.13 && <5, deepseq >=1.4.3 && <2, hashable >=1.2 && <2, semigroupoids >=5.3 && <7,@@ -69,7 +72,7 @@ default-language: Haskell2010 main-is: Main.hs build-depends:- rerebase <2,+ rerebase >=1.23 && <2, strict-list,- tasty >=0.12 && <2,- tasty-quickcheck >=0.9 && <0.12,+ tasty >=1.5.4 && <1.6,+ tasty-quickcheck >=0.11.1 && <0.12,
test/Main.hs view
@@ -4,11 +4,10 @@ import qualified Data.List as Lazy import qualified Data.Maybe as Maybe-import GHC.Exts as Exports (IsList (..)) import StrictList import Test.Tasty import Test.Tasty.QuickCheck-import Prelude hiding (List, break, choose, drop, dropWhile, filter, head, init, last, reverse, span, tail, take, takeWhile, toList)+import Prelude hiding (break, choose, drop, dropWhile, filter, fromList, head, init, last, reverse, span, tail, take, takeWhile, toList) main :: IO () main =@@ -21,7 +20,11 @@ testProperty "fromList" $ forAll lazyListGen $ \lazy ->- toList (fromList @(List Word8) lazy) === lazy,+ toList (fromList lazy) === lazy,+ testProperty "compare"+ $ forAll ((,) <$> strictAndLazyListGen <*> strictAndLazyListGen)+ $ \((strict1, lazy1), (strict2, lazy2)) ->+ compare strict1 strict2 === compare lazy1 lazy2, testProperty "reverse" $ forAll strictAndLazyListGen $ \(strict, lazy) ->@@ -95,8 +98,8 @@ $ \(strict, lazy) -> toList (initReversed strict) === Lazy.reverse (Lazy.take (Lazy.length lazy - 1) lazy), testProperty "fromListReversed"- $ forAll strictAndLazyListGen- $ \(strict, lazy) ->+ $ forAll lazyListGen+ $ \lazy -> toList (fromListReversed lazy) === Lazy.reverse lazy, testProperty "prependReversed" $ forAll ((,) <$> strictAndLazyListGen <*> strictAndLazyListGen)@@ -189,8 +192,8 @@ instance Show (Word8 -> Bool) where show _ = "(Word8 -> Bool) function" -instance Show (Word8 -> List Word8) where- show _ = "(Word8 -> List Word8) function"+instance Show (Word8 -> StrictList Word8) where+ show _ = "(Word8 -> StrictList Word8) function" instance Show (Word8 -> [Word8]) where show _ = "(Word8 -> [Word8]) function"