packages feed

mono-hash-0.1.0.0: src/Data/Hash/Mono/FNV.hs

{-# LANGUAGE BangPatterns #-}

-- | FNV-1 and FNV-1a hashes for any 'MonoFoldable' container.
module Data.Hash.Mono.FNV (
    FNVData,
    FNVResult,
    HasFNVBasis,
    fnv1,
    fnv1a,
)
where

import Data.Bits (Bits (xor))
import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
import Data.MonoTraversable (Element, MonoFoldable (ofoldl'))
import Data.WideWord.Word128 (Word128)
import Data.WideWord.Word256 (Word256)
import Data.Word (Word32, Word64, Word8)

-- | Offset basis and prime for an FNV result width.
class HasFNVBasis a where
    -- | Offset basis used to seed the hash.
    fnvOffsetBasis :: a

    -- | Prime applied once per input byte.
    fnvPrime :: a

instance HasFNVBasis Word32 where
    fnvOffsetBasis :: Word32
    fnvOffsetBasis = 2166136261

    fnvPrime :: Word32
    fnvPrime = 16777619

instance HasFNVBasis Word64 where
    fnvOffsetBasis :: Word64
    fnvOffsetBasis = 14695981039346656037

    fnvPrime :: Word64
    fnvPrime = 1099511628211

instance HasFNVBasis Word128 where
    fnvOffsetBasis :: Word128
    fnvOffsetBasis = 144066263297769815596495629667062367629

    fnvPrime :: Word128
    fnvPrime = 309485009821345068724781371

instance HasFNVBasis Word256 where
    fnvOffsetBasis :: Word256
    fnvOffsetBasis = 100029257958052580907070968620625704837092796014241193945225284501741471925557

    fnvPrime :: Word256
    fnvPrime = 374144419156711147060143317175368453031918731002211

-- | Constraint for types that can be used as an FNV result.
type FNVResult a = (HasFNVBasis a, Bits a, Num a)

-- | Constraint for containers hashed with FNV.
type FNVData a = (MonoFoldable a, ByteFoldable (Element a))

fnvGeneric :: forall result mono. (FNVData mono, FNVResult result) => (result -> Word8 -> result) -> mono -> result
fnvGeneric fnvRound = ofoldl' (foldlBytes fnvRound) fnvOffsetBasis
{-# INLINE fnvGeneric #-}

{- | FNV-1 hash of a monomorphic container.

Example:
@
fnv1 \@'Word64' \"hello\"
@
-}
fnv1 :: forall result mono. (FNVData mono, FNVResult result) => mono -> result
fnv1 = fnvGeneric fnvRound
  where
    fnvRound :: result -> Word8 -> result
    fnvRound !acc !octet = (acc * fnvPrime) `xor` fromIntegral octet
{-# INLINEABLE fnv1 #-}

{- | FNV-1a hash of a monomorphic container.

Example:
@
fnv1a \@'Word64' \"hello\"
@
-}
fnv1a :: forall result mono. (FNVData mono, FNVResult result) => mono -> result
fnv1a = fnvGeneric fnvRound
  where
    fnvRound :: result -> Word8 -> result
    fnvRound !acc !octet = (acc `xor` fromIntegral octet) * fnvPrime
{-# INLINEABLE fnv1a #-}