Implementation of ULID - Universally Unique Lexicographically Sortable Identifier.
Implementation of Alizain Feerasta's ULID specification. A 26 character string identifier, as opposed to the 36 character UUID string. Uses Douglas Crockford's base 32 encoding for better efficiency and readability (5 bits per character).
ULID Implementation in Haskell
Lexicographically sortable, 128-bit identifier with 48-bit timestamp and 80 random bits. Canonically encoded as a 26 character string, as opposed to the 36 character UUID.
Original implementation and spec: github.com/alizain/ulid
01an4z07by 79ka1307sr9x4mv3
|----------| |----------------|
Timestamp Randomness
48 bits 80 bits
Universally Unique Lexicographically Sortable Identifier
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 Douglas Crockford's base 32 for better efficiency and readability (5 bits per character)
- Case insensitive
- No special characters (URL safe)
Usage
A simple usage example:
module Main where
import Data.ULID
main :: IO ()
main = do
-- Derive a ULID using the current time and default random number generator
ulid1 <- getULID
print ulid1
-- Derive a ULID using a specified time and default random number generator
ulid2 <- getULIDTime 1469918176.385 -- POSIX Time, millisecond precision
print ulid2
As per the spec, it is also possible to use a cryptographically-secure random number generator to contribute the randomness. However, the programmer must manage the generator on their own.
Example:
module Main where
import Data.ULID
import qualified Crypto.Random as CR
import qualified Data.ULID.Random as UR
import qualified Data.ULID.TimeStamp as TS
main :: IO ()
main = do
-- This default instantiation may not be sufficiently secure.
-- See the docs at
-- hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html
g <- (CR.newGenIO :: IO CR.SystemRandom)
-- Generate timestamp from current time
t <- TS.getULIDTimeStamp
let ulid3 = case UR.mkCryptoULIDRandom g of
Left err -> error $ show err
-- use g2, …, to continue generating secure ULIDs
Right (rnd, g2) -> ULID t rnd
print ulid3
Test Suite
stack test
Performance
stack bench
Running 1 benchmarks...
Benchmark ulid-bench: RUNNING...
217,868 op/s generate
Benchmark ulid-bench: FINISH