packages feed

pubgrub-0.1.0.0: src/PubGrub/Version.hs

-- | Versions: a list of numbers, such as @1.2.0@, ordered component by
-- component, for a domain whose values are versions. "PubGrub.VersionSet" is
-- the matching set type.
--
-- __Trailing zeros are insignificant__: @1.2@, @1.2.0@ and @1.2.0.0@ are one
-- version, equal under 'Eq' and 'Ord'. Each keeps its spelling for display
-- ('versionNumbers'), and the comparisons read the 'normalForm', the spelling
-- with its trailing zeros dropped.
--
-- An order in which @1.2 < 1.2.0@ would put room between two spellings of one
-- version, where no version can ever be, and every set would have to carry
-- it: @>=1.2 && <1.2.0@ would be a range that looks open and holds nothing,
-- which a search can only discover by asking. With trailing zeros
-- insignificant there is no such range, and a bound @>=1.8.0@ admits the
-- version spelled @1.8@, as its author means. Two values that spell one version
-- differently are one value here, so a domain that has both has to keep one.
module PubGrub.Version
    ( Version
    , mkVersion
    , versionNumbers
    , normalForm
    , version0
    ) where

import           Data.Ord (comparing)

-- | A version: its spelling, and its normal form. Build one with 'mkVersion'.
data Version = Version
    { versionNumbers :: ![Int]
    -- ^ the components as spelled — @[1,2,0,0]@ for @1.2.0.0@, not the normal
    -- form
    , normalForm     :: ![Int]
    -- ^ the spelling with trailing zeros dropped, keeping at least one
    -- component. What 'Eq' and 'Ord' compare.
    }

-- The normal form is a field, computed once by 'mkVersion', because 'Eq' and
-- 'Ord' read it on every comparison and a solve compares versions far more
-- often than it makes them. Deriving it on each comparison cost a fifth of a
-- large solve's time and a third of its allocation.

instance Eq Version where
    a == b = normalForm a == normalForm b

instance Ord Version where
    compare = comparing normalForm

-- | As the expression that makes it: @mkVersion [1,2,0]@.
instance Show Version where
    showsPrec d v =
        showParen (d > 10) (showString "mkVersion " . showsPrec 11 (versionNumbers v))

-- | A version from its components, as spelled. An empty list is the version
-- @0@, and a negative component is taken as @0@.
mkVersion :: [Int] -> Version
mkVersion [] = version0
mkVersion ns = Version spelled normal
  where
    spelled = map (max 0) ns
    normal = case reverse (dropWhile (== 0) (reverse spelled)) of
        [] -> [0]
        ms -> ms

-- | The version @0@, the lowest there is. Every version set starts here.
version0 :: Version
version0 = Version [0] [0]