range 0.2.0.0 → 0.2.1.0
raw patch · 8 files changed
+298/−82 lines, 8 files
Files
- Data/Range/Algebra.hs +22/−0
- Data/Range/Algebra/Internal.hs +3/−0
- Data/Range/NestedRange.hs +64/−6
- Data/Range/Parser.hs +36/−18
- Data/Range/Range.hs +124/−18
- Data/Range/RangeInternal.hs +25/−26
- Data/Range/Util.hs +11/−6
- range.cabal +13/−8
Data/Range/Algebra.hs view
@@ -9,6 +9,27 @@ -- evaluate them all at once. This is not only useful for efficiency but for -- parsing too. Build up @RangeExpr@'s whenever you wish to perform multiple -- operations in a row, and evaluate it in one step to be as efficient as possible.+--+-- __Note:__ This module is based on F-Algebras to do much of the heavy conceptual+-- lifting. If you have never seen F-Algebras before then I highly recommend reading+-- through <https://www.schoolofhaskell.com/user/bartosz/understanding-algebras this introductory content>+-- from the School of Haskell.+--+-- == Examples+--+-- A simple example of using this module would look like this:+--+-- @+-- ghci> import qualified Data.Range.Algebra as A+-- ghci> (A.eval . A.invert $ A.const [SingletonRange 5]) :: [Range Integer]+-- [LowerBoundRange 6,UpperBoundRange 4]+-- (0.01 secs, 597,656 bytes)+-- ghci>+-- @+--+-- You can also use this module to evaluate range predicates.+--+-- module Data.Range.Algebra ( RangeExpr -- ** Operations@@ -50,6 +71,7 @@ -- of a range, so that a range expression of the same type can be evaluated, yielding -- that representation. class RangeAlgebra a where+ -- | This function is used to convert your built expressions into ranges. eval :: Algebra RangeExpr a -- | Multiple ranges represented by a list of disjoint ranges.
Data/Range/Algebra/Internal.hs view
@@ -49,6 +49,9 @@ newtype RangeExpr a = RangeExpr { getFree :: Free RangeExprF a } deriving (Show, Eq, Functor) +-- | This is an F-Algebra. You don't need to know what this is in order to be able+-- to use this module, but, if you are interested you can+-- <https://www.schoolofhaskell.com/user/bartosz/understanding-algebras read more on School of Haskell>. type Algebra f a = f a -> a rangeMergeAlgebra :: (Ord a, Enum a) => Algebra RangeExprF (RangeMerge a)
Data/Range/NestedRange.hs view
@@ -1,24 +1,69 @@ {-# LANGUAGE Safe #-} -- | Nested Ranges are common in practical usage. They appear in such forms as library--- version numbers ("Version 1.4.5.6" for example). And it is very useful to be able to--- compare these ranges to one another. This module exists for the purpose of allowing--- these comparisons between nested ranges. The module builds upon the basic range concept--- from other parts of this library.-module Data.Range.NestedRange where+-- version numbers ("Version 1.4.5.6" for example).+--+-- It is very useful to be able to compare these ranges to one another. This module exists+-- for the purpose of allowing these comparisons between nested ranges. The module builds+-- upon the basic range concept from other parts of this library.+module Data.Range.NestedRange+ ( NestedRange(..)+ , fromVersion+ , inNestedRange+ ) where import Data.Range.Range+import Data.Version -- | The Nested Range is a structure that in a nested form of many ranges where there can -- be multiple ranges at every level.+--+-- For example, saying that you require a minimum version of 1.2.3 could be represented as:+--+-- @+-- NestedRange [[LowerBoundRange 1],[LowerBoundRange 2],[LowerBoundRange 3]]+-- @ data NestedRange a = NestedRange [[Range a]]-+ deriving(Eq, Show) -- I wanted to know if a nested number of elements are in a given range. That way I can -- just immediately run a single function and tell things about ranges. -- | Given a list of nested values and a nested range tell us wether the nested value -- exists inside the nested range.+--+-- == Examples+--+-- In a simple case:+--+-- @+-- ghci> inNestedRange [2, 8, 3] (NestedRange [[SpanRange 1 2]] :: NestedRange Integer)+-- True+-- (0.01 secs, 558,400 bytes)+-- ghci>+-- @+--+-- Not in the bounds:+--+-- @+-- ghci> inNestedRange [2, 8, 3] (NestedRange [[SpanRange 1 2], [UpperBoundRange 7]] :: NestedRange Integer)+-- False+-- (0.00 secs, 558,896 bytes)+-- ghci>+-- @+--+-- For something based on Data.Version:+--+-- @+-- ghci> version = Version [2, 8, 3] []+-- ghci> upperBound = Version [2, 7] []+-- ghci> inNestedRange (versionBranch version) (fromVersion UpperBoundRange upperBound)+-- False+-- ghci>+-- ghci> inNestedRange (versionBranch version) (fromVersion LowerBoundRange upperBound)+-- True+-- ghci>+-- @ inNestedRange :: Ord a => [a] -> NestedRange a -> Bool inNestedRange values (NestedRange ranges) = go values ranges where@@ -27,3 +72,16 @@ go _ [] = True -- If you have already found the values you have to be in range then they are go [] _ = False -- If you have not fully matched it yet then it is not in range. go (value : vs) (range : rs) = inRanges range value && go vs rs++-- | This method converts the "Data.Version" datatype into a "NestedRange".+--+-- For example:+--+-- @+-- ghci> fromVersion LowerBoundRange (Version [1, 2, 3] [])+-- NestedRange [[LowerBoundRange 1],[LowerBoundRange 2],[LowerBoundRange 3]]+-- (0.01 secs, 624,736 bytes)+-- ghci>+-- @+fromVersion :: (Int -> Range Int) -> Version -> NestedRange Int+fromVersion bound = NestedRange . fmap (return . bound) . versionBranch
Data/Range/Parser.hs view
@@ -1,17 +1,30 @@ {-# LANGUAGE FlexibleContexts #-} --- | It should not be unexpected that you will be given a string representation of some--- ranges and you will need to parse them so that you can then do some further processing.--- This parser exists in order to make the most common forms of range strings easy to--- parse. It does not cover all cases however but you should not be too worried about--- that because you should be able to write your own parser using parsec or Alex/Happy and--- then you can convert everything that you parse into a RangeTree object for easier--- processing.-module Data.Range.Parser +-- | This package provides a simple range parser.+--+-- This range parser was designed to be a useful tool for CLI programs. For example, by+-- default, this example depicts how the parser works:+--+-- @+-- ghci> parseRanges "-5,8-10,13-15,20-" :: Either ParseError [Range Integer]+-- Right [UpperBoundRange 5,SpanRange 8 10,SpanRange 13 15,LowerBoundRange 20]+-- (0.01 secs, 681,792 bytes)+-- ghci>+-- @+--+-- And the * character translates to an infinite range. This is very useful for accepting+-- ranges as input in CLI programs, but not as useful for parsing .cabal or package.json files.+--+-- To handle more complex parsing cases it is recommended that you use the ranges library+-- in conjunction with parsec or Alex/Happy and convert the versions that you find into+-- ranges.+module Data.Range.Parser ( parseRanges- , ranges+ , customParseRanges , RangeParserArgs(..) , defaultArgs+ , ranges+ , ParseError ) where import Text.Parsec@@ -19,9 +32,8 @@ import Data.Range.Range --- | The arguments that are used, and can be modified, while parsing a standard range--- string.-data RangeParserArgs = Args +-- | These are the arguments that will be used when parsing a string as a range.+data RangeParserArgs = Args { unionSeparator :: String -- ^ A separator that represents a union. , rangeSeparator :: String -- ^ A separator that separates the two halves of a range. , wildcardSymbol :: String -- ^ A separator that implies an unbounded range.@@ -30,28 +42,34 @@ -- | These are the default arguments that are used by the parser. Please feel free to use -- the default arguments for you own parser and modify it from the defaults at will.-defaultArgs :: RangeParserArgs +defaultArgs :: RangeParserArgs defaultArgs = Args { unionSeparator = "," , rangeSeparator = "-" , wildcardSymbol = "*" } --- | Given a string this function will either return a parse error back to the user or the--- list of ranges that are represented by the parsed string.+-- | Given a string, this function will either return a parse error back to the user or the+-- list of ranges that are represented by the parsed string. Very useful for CLI programs+-- that need to load ranges from a single-line string. parseRanges :: (Read a) => String -> Either ParseError [Range a] parseRanges = parse (ranges defaultArgs) "(range parser)" +-- | If you disagree with the default characters for separating ranges then this function can+-- be used to customise them, up to a point.+customParseRanges :: Read a => RangeParserArgs -> String -> Either ParseError [Range a]+customParseRanges args = parse (ranges args) "(range parser)"+ string_ :: Stream s m Char => String -> ParsecT s u m () string_ x = string x >> return () --- | Given the parser arguments this returns a parser that is capable of parsing a list of+-- | Given the parser arguments this returns a parsec parser that is capable of parsing a list of -- ranges. ranges :: (Read a) => RangeParserArgs -> Parser [Range a] ranges args = range `sepBy` (string $ unionSeparator args)- where + where range :: (Read a) => Parser (Range a)- range = choice + range = choice [ infiniteRange , spanRange , singletonRange
Data/Range/Range.hs view
@@ -1,17 +1,20 @@ {-# LANGUAGE Safe #-} --- | This entire library is concerned with ranges and this module implements the absolute--- basic range functions.+-- | This module provides a simple api to access range functionality. It provides standard+-- set operations on ranges, the ability to merge ranges together and, importantly, the ability+-- to check if a value is within a range.+--+-- __Note:__ It is intended that you will read the documentation in this module from top to bottom. module Data.Range.Range ( Range(..), inRange, inRanges, rangesOverlap, mergeRanges,- invert, union, intersection, difference,+ invert, fromRanges ) where @@ -21,23 +24,59 @@ -- | Performs a set union between the two input ranges and returns the resultant set of -- ranges.+--+-- For example:+--+-- @+-- ghci> union [SpanRange 1 10] [SpanRange 5 (15 :: Integer)]+-- [SpanRange 1 15]+-- (0.00 secs, 587,152 bytes)+-- ghci>+-- @ union :: (Ord a, Enum a) => [Range a] -> [Range a] -> [Range a] union a b = Alg.eval $ Alg.union (Alg.const a) (Alg.const b) {-# INLINE union #-} -- | Performs a set intersection between the two input ranges and returns the resultant set of -- ranges.+--+-- For example:+--+-- @+-- ghci> intersection [SpanRange 1 10] [SpanRange 5 (15 :: Integer)]+-- [SpanRange 5 10]+-- (0.00 secs, 584,616 bytes)+-- ghci>+-- @ intersection :: (Ord a, Enum a) => [Range a] -> [Range a] -> [Range a] intersection a b = Alg.eval $ Alg.intersection (Alg.const a) (Alg.const b) {-# INLINE intersection #-} -- | Performs a set difference between the two input ranges and returns the resultant set of -- ranges.+--+-- For example:+--+-- @+-- ghci> difference [SpanRange 1 10] [SpanRange 5 (15 :: Integer)]+-- [SpanRange 1 4]+-- (0.00 secs, 590,424 bytes)+-- ghci>+-- @ difference :: (Ord a, Enum a) => [Range a] -> [Range a] -> [Range a] difference a b = Alg.eval $ Alg.difference (Alg.const a) (Alg.const b) {-# INLINE difference #-} -- | An inversion function, given a set of ranges it returns the inverse set of ranges.+--+-- For example:+--+-- @+-- ghci> invert [SpanRange 1 10, SpanRange 15 (20 :: Integer)]+-- [LowerBoundRange 21,UpperBoundRange 0,SpanRange 11 14]+-- (0.00 secs, 623,456 bytes)+-- ghci>+-- @ invert :: (Ord a, Enum a) => [Range a] -> [Range a] invert = Alg.eval . Alg.invert . Alg.const {-# INLINE invert #-}@@ -60,6 +99,24 @@ -- | Given a range and a value it will tell you wether or not the value is in the range. -- Remember that all ranges are inclusive.+--+-- The primary value of this library is performance and this method can be used to show+-- this quite clearly. For example, you can try and approximate basic range functionality+-- with "Data.List.elem" so we can generate an apples to apples comparison in GHCi:+--+-- @+-- ghci> :set +s+-- ghci> elem (10000000 :: Integer) [1..10000000]+-- True+-- (0.26 secs, 720,556,888 bytes)+-- ghci> inRange (SpanRange 1 10000000) (10000000 :: Integer)+-- True+-- (0.00 secs, 557,656 bytes)+-- ghci>+-- @+--+-- As you can see, this function is significantly more performant, in both speed and memory,+-- than using the elem function. inRange :: (Ord a) => Range a -> a -> Bool inRange (SingletonRange a) value = value == a inRange (SpanRange x y) value = isBetween value (x, y)@@ -72,30 +129,79 @@ inRanges :: (Ord a) => [Range a] -> a -> Bool inRanges rs a = any (`inRange` a) rs --- | When you create a range there may be overlaps in your ranges. However, for the sake--- of efficiency you probably want the list of ranges with no overlaps. The mergeRanges--- function takes a set of ranges and returns the same set specified by the minimum number--- of Range objects. A useful function for cleaning up your ranges. Please note that, if--- you use any of the other operations on sets of ranges like invert, union and--- intersection then this is automatically done for you. Which means that a function like--- this is redundant: mergeRanges . intersection+-- | An array of ranges may have overlaps; this function will collapse that array into as few+-- Ranges as possible. For example:+--+-- @+-- ghci> mergeRanges [LowerBoundRange 12, SpanRange 1 10, SpanRange 5 (15 :: Integer)]+-- [LowerBoundRange 1]+-- (0.01 secs, 588,968 bytes)+-- ghci>+-- @+--+-- As you can see, the mergeRanges method collapsed multiple ranges into a single range that+-- still covers the same surface area.+--+-- This may be useful for a few use cases:+--+-- * You are hyper concerned about performance and want to have the minimum number of ranges+-- for comparison in the inRanges function.+-- * You wish to display ranges to a human and want to show the minimum number of ranges to+-- avoid having to make people perform those calculations themselves.+--+-- Please note that the use of any of the operations on sets of ranges like invert, union and+-- intersection will have the same behaviour as mergeRanges as a side effect. So, for example,+-- this is redundant:+--+-- @+-- mergeRanges . intersection []+-- @ mergeRanges :: (Ord a, Enum a) => [Range a] -> [Range a]-mergeRanges = Alg.eval . Alg.const+mergeRanges = Alg.eval . Alg.union (Alg.const []) . Alg.const {-# INLINE mergeRanges #-} --- | A set of ranges represents a collection of real values without actually instantiating--- those values. This allows you to have infinite ranges. However, sometimes you wish to--- actually get the values that your range represents, or even get a sample set of the--- values. This function generates as many of the values that belong to your range as you--- like.+-- | Instantiate all of the values in a range.+--+-- __Warning__: This method is meant as a convenience method, it is not efficient.+--+-- A set of ranges represents a collection of real values without actually instantiating+-- those values. Not instantiating ranges, allows the range library to support infinite+-- ranges and be super performant.+--+-- However, sometimes you actually want to get the values that your range represents, or even+-- get a sample set of the values. This function generates as many of the values that belong+-- to your range as you like.+--+-- Because ranges can be infinite, it is highly recommended to combine this method with something like+-- "Data.List.take" to avoid an infinite recursion.+--+-- == Examples+--+-- A simple span:+--+-- @+-- ghci> take 5 . fromRanges $ [SpanRange 1 10 :: Range Integer]+-- [1,2,3,4,5]+-- (0.01 secs, 566,016 bytes)+-- ghci>+-- @+--+-- An infinite range:+--+-- @+-- ghci> take 5 . fromRanges $ [InfiniteRange :: Range Integer]+-- [0,1,-1,2,-2]+-- (0.00 secs, 566,752 bytes)+-- ghci>+-- @ fromRanges :: (Ord a, Enum a) => [Range a] -> [a]-fromRanges = concatMap fromRange+fromRanges = takeEvenly . fmap fromRange . mergeRanges where fromRange range = case range of SingletonRange x -> [x] SpanRange a b -> [a..b] LowerBoundRange x -> iterate succ x UpperBoundRange x -> iterate pred x- InfiniteRange -> zero : takeEvenly (tail $ iterate succ zero) (tail $ iterate pred zero)+ InfiniteRange -> zero : takeEvenly [tail $ iterate succ zero, tail $ iterate pred zero] where zero = toEnum 0
Data/Range/RangeInternal.hs view
@@ -3,7 +3,6 @@ module Data.Range.RangeInternal where import Data.Maybe (catMaybes)---import Data.Ord (comparing) import Data.Range.Data import Data.Range.Spans@@ -14,7 +13,7 @@ - functions so that we can reason about what we are given. - - RangeMerge assumptions:- - * The span ranges will never overlap the bounds. + - * The span ranges will never overlap the bounds. - * The span ranges are always sorted in ascending order by the first element. - * The lower and upper bounds never overlap in such a way to make it an infinite range. -}@@ -48,8 +47,8 @@ exportRangeMerge rm = putAll rm where putAll IRM = [InfiniteRange]- putAll (RM lb up spans) = - putLowerBound lb ++ putUpperBound up ++ putSpans spans+ putAll (RM lb up spans) =+ putUpperBound up ++ putSpans spans ++ putLowerBound lb putLowerBound = maybe [] (return . LowerBoundRange) putUpperBound = maybe [] (return . UpperBoundRange)@@ -64,7 +63,7 @@ intersectSpansRM :: (Ord a) => RangeMerge a -> RangeMerge a -> RangeMerge a intersectSpansRM one two = RM Nothing Nothing newSpans where- newSpans = intersectSpans (spanRanges one) (spanRanges two) + newSpans = intersectSpans (spanRanges one) (spanRanges two) intersectWith :: (Ord a) => (a -> (a, a) -> Maybe (a, a)) -> Maybe a -> [(a, a)] -> [(a, a)] intersectWith _ Nothing _ = []@@ -88,14 +87,14 @@ , largestUpperBound = newUpperBound , spanRanges = joinedSpans }- where + where lowerOneSpans = intersectWith fixLower (largestLowerBound one) (spanRanges two) lowerTwoSpans = intersectWith fixLower (largestLowerBound two) (spanRanges one) upperOneSpans = intersectWith fixUpper (largestUpperBound one) (spanRanges two) upperTwoSpans = intersectWith fixUpper (largestUpperBound two) (spanRanges one)- intersectedSpans = intersectSpans (spanRanges one) (spanRanges two) + intersectedSpans = intersectSpans (spanRanges one) (spanRanges two) - sortedResults = foldr1 insertionSortSpans + sortedResults = foldr1 insertionSortSpans [ lowerOneSpans , lowerTwoSpans , upperOneSpans@@ -109,10 +108,10 @@ newLowerBound = calculateNewBound largestLowerBound max one two newUpperBound = calculateNewBound largestUpperBound min one two - calculateNewBound - :: (Ord a) - => (RangeMerge a -> Maybe a) - -> (a -> a -> a) + calculateNewBound+ :: (Ord a)+ => (RangeMerge a -> Maybe a)+ -> (a -> a -> a) -> RangeMerge a -> RangeMerge a -> Maybe a calculateNewBound ext comp one two = case (ext one, ext two) of (Just x, Just y) -> Just $ comp x y@@ -123,17 +122,17 @@ calculateBoundOverlap one two = catMaybes [oneWay, secondWay] where oneWay = case (largestLowerBound one, largestUpperBound two) of- (Just x, Just y) -> if y >= x + (Just x, Just y) -> if y >= x then Just (x, y) else Nothing _ -> Nothing secondWay = case (largestLowerBound two, largestUpperBound one) of- (Just x, Just y) -> if y >= x + (Just x, Just y) -> if y >= x then Just (x, y) else Nothing _ -> Nothing- + unionRangeMerges :: (Ord a, Enum a) => RangeMerge a -> RangeMerge a -> RangeMerge a unionRangeMerges IRM _ = IRM unionRangeMerges _ IRM = IRM@@ -141,10 +140,10 @@ where filterOne = foldr filterLowerBound boundedRM joinedSpans filterTwo = foldr filterUpperBound (filterOne { spanRanges = [] }) (spanRanges filterOne)- + infiniteCheck :: (Ord a, Enum a) => RangeMerge a -> RangeMerge a infiniteCheck IRM = IRM- infiniteCheck rm@(RM (Just x) (Just y) _) = if x <= succ y + infiniteCheck rm@(RM (Just x) (Just y) _) = if x <= succ y then IRM else rm infiniteCheck rm = rm@@ -161,10 +160,10 @@ , spanRanges = [] } - calculateNewBound - :: (Ord a) - => (RangeMerge a -> Maybe a) - -> (a -> a -> a) + calculateNewBound+ :: (Ord a)+ => (RangeMerge a -> Maybe a)+ -> (a -> a -> a) -> RangeMerge a -> RangeMerge a -> Maybe a calculateNewBound ext comp one two = case (ext one, ext two) of (Just x, Just y) -> Just $ comp x y@@ -174,7 +173,7 @@ filterLowerBound :: (Ord a, Enum a) => (a, a) -> RangeMerge a -> RangeMerge a filterLowerBound _ IRM = IRM filterLowerBound a rm@(RM Nothing _ _) = rm { spanRanges = a : spanRanges rm }-filterLowerBound s@(lower, _) rm@(RM (Just lowestBound) _ _) = +filterLowerBound s@(lower, _) rm@(RM (Just lowestBound) _ _) = case boundCmp lowestBound s of GT -> rm { spanRanges = s : spanRanges rm } LT -> rm@@ -196,7 +195,7 @@ appendSpanRM :: (Ord a, Enum a) => (a, a) -> RangeMerge a -> RangeMerge a appendSpanRM _ IRM = IRM-appendSpanRM sp@(lower, higher) rm = +appendSpanRM sp@(lower, higher) rm = if (newUpper, newLower) == (lub, llb) && isLower lower newLower && (Just higher) > newUpper then newRangesRM { spanRanges = sp : spanRanges rm@@ -205,7 +204,7 @@ { spanRanges = spanRanges rm } where- newRangesRM = rm + newRangesRM = rm { largestLowerBound = newLower , largestUpperBound = newUpper }@@ -257,7 +256,7 @@ Just upper -> [(succ upper, newUpperValue)] lowerSpan = case largestLowerBound rm of Nothing -> []- Just lower -> [(newLowerValue, pred lower)] + Just lower -> [(newLowerValue, pred lower)] betweenSpans = invertSpans . spanRanges $ rm @@ -272,7 +271,7 @@ {- intersectSpansRM :: (Ord a) => RangeMerge a -> (a, a) -> [(a, a)] intersectSpansRM rm sp@(lower, upper) = intersectedSpans- where + where spans = spanRanges rm intersectedSpans = catMaybes $ map (intersectCompareSpan sp) spans
Data/Range/Util.hs view
@@ -2,6 +2,8 @@ module Data.Range.Util where +import Data.Maybe (catMaybes)+ -- This module is supposed to contain all of the functions that are required by the rest -- of the code but could be easily pulled into separate and completely non-related -- codebases or libraries.@@ -9,7 +11,7 @@ insertionSort :: (Ord a) => (a -> a -> Ordering) -> [a] -> [a] -> [a] insertionSort comp xs ys = go xs ys where- go (f : fs) (s : ss) = case comp f s of + go (f : fs) (s : ss) = case comp f s of LT -> f : go fs (s : ss) EQ -> f : s : go fs ss GT -> s : go (f : fs) ss@@ -19,11 +21,14 @@ isBetween :: (Ord a) => a -> (a, a) -> Bool isBetween a (x, y) = (x <= a) && (a <= y) -takeEvenly :: [a] -> [a] -> [a]-takeEvenly (a : as) (b : bs) = a : b : takeEvenly as bs-takeEvenly xs [] = xs-takeEvenly [] xs = xs- +takeEvenly :: [[a]] -> [a]+takeEvenly [] = []+takeEvenly xss = (catMaybes . map safeHead $ xss) ++ takeEvenly (filter (not . null) . map tail $ xss)++safeHead :: [a] -> Maybe a+safeHead [] = Nothing+safeHead (x : _) = Just x+ pairs :: [a] -> [(a, a)] pairs [] = [] pairs xs = zip xs (tail xs)
range.cabal view
@@ -10,18 +10,23 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version: 0.2.0.0+version: 0.2.1.0 -- A short (one-line) description of the package.-synopsis: This has a bunch of code for specifying and managing ranges in your code.+synopsis: An efficient and versatile range library. -- A longer description of the package.-description: range is built to allow you to use ranges in your code quickly and- efficiently. There are many occasions where you will want to check if- certain values are within a range and this library will make it- trivial for you to do so. It also attempts to do so in the most- efficient way possible.+description: The range library alows the use of performant and versatile ranges in your code.+ It supports bounded and unbounded ranges, ranges in a nested manner (like library+ versions), an efficient algebra of range computation and even a simplified interface+ for ranges for the common cases. This library is far more efficient than using the+ default Data.List functions to approximate range behaviour. Performance is the major+ value offering of this library. + If this is your first time using this library it is highly recommended that you start+ with "Data.Range.Range"; it contains the basics of this library that meet most use+ cases.+ homepage: https://bitbucket.org/robertmassaioli/range -- The license under which the package is released.@@ -67,7 +72,7 @@ -- Other library packages from which modules are imported.- build-depends: base >= 4.5 && < 5+ build-depends: base >= 4.7 && < 5 , parsec >= 3 , free >=4.12