ulid-0.2.0.0: src/Data/ULID.hs
{- |
Module : Data.ULID
Copyright : (c) 2017 Steve Kollmansberger
License : BSD-style
Maintainer : steve@kolls.net
Stability : experimental
Portability : portable
This library implements the Universally Unique Lexicographically Sortable Identifier, as described at <https://github.com/alizain/ulid>.
UUID can be suboptimal for many uses-cases because:
* It isn't the most character efficient way of encoding 128 bits of randomness
* UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
* UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
* UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
Instead, herein is proposed ULID:
* 128-bit compatibility with UUID
* 1.21e+24 unique ULIDs per millisecond
* Lexicographically sortable!
* Canonically encoded as a 26 character string, as opposed to the 36 character UUID
* Uses Crockford's base32 for better efficiency and readability (5 bits per character)
* Case insensitive
* No special characters (URL safe)
-}
{-# LANGUAGE DeriveDataTypeable #-}
module Data.ULID (
ULID(..),
getULIDTime,
getULID,
ulidToInteger,
ulidFromInteger
) where
import Control.DeepSeq
import Data.Binary
import qualified Data.ByteString.Lazy as LBS
import Data.Data
import Data.Hashable
import Data.Monoid ((<>))
import Data.Time.Clock.POSIX
import System.IO.Unsafe
import qualified System.Random as R
import Data.Binary.Roll
import Data.ULID.Random
import Data.ULID.TimeStamp
data ULID = ULID {
timeStamp :: !ULIDTimeStamp,
random :: !ULIDRandom
}
deriving (Eq, Typeable, Data)
-- | Derive a ULID using a specified time and default random number generator
getULIDTime :: POSIXTime -- ^ The specified UNIX time (seconds) to millisecond precision, e.g. 1469918176.385
-> IO ULID
getULIDTime t = do
let t' = mkULIDTimeStamp t
r <- getULIDRandom
return $ ULID t' r
-- | Derive a ULID using the current time and default random number generator
getULID :: IO ULID
getULID = do
t <- getULIDTimeStamp
r <- getULIDRandom
return $ ULID t r
-- | Convert a ULID to its corresponding (at most) 128-bit Integer. Integer equivalents retain sortable trait (same sort order).
-- This could be useful for storing in a database using a smaller field than storing the Show'n string, but still human-readable unlike the Binary version.
ulidToInteger :: ULID -> Integer
ulidToInteger = roll.(LBS.unpack).encode
-- | Convert a ULID from its corresponding 128-bit Integer.
ulidFromInteger :: Integer -- ^ The ULID's Integer equivalent, as generated by toInteger
-> ULID
ulidFromInteger = decode.(LBS.pack).(unroll 16) -- 16 bytes = 128 bit
instance Ord ULID where
compare (ULID ts1 _) (ULID ts2 _) = compare ts1 ts2
instance Show ULID where
show (ULID ts bytes) = (show ts) ++ (show bytes)
instance Read ULID where
readsPrec _ str = do
(ts, str2) <- reads str
(rn, str3) <- reads str2
return (ULID ts rn, str3)
instance Binary ULID where
put (ULID ts bytes) = put ts <> put bytes
get = do
ts <- get
bytes <- get
return $ ULID ts bytes
-- Because of the strictness annotations, this shouldn't be needed and shouldn't do anything
-- I tested and confirmed this in the benchmark, but since I did the work to put it here
-- It's no harm to leave it in
instance NFData ULID where
rnf (ULID ts bytes) = rnf ts `seq` (rnf bytes `seq` ())
instance R.Random ULID where
randomR _ = R.random -- ignore range
random g = unsafePerformIO $ do
t <- getULIDTimeStamp
let (r, g') = mkULIDRandom g
return (ULID t r, g')
randomIO = getULID
instance Hashable ULID where
hashWithSalt salt ulid = hashWithSalt salt (encode ulid)