hashable 1.4.2.0 → 1.4.3.0
raw patch · 7 files changed
+74/−27 lines, 7 filesdep ~basedep ~bytestringdep ~deepseqPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base, bytestring, deepseq, text
API changes (from Hackage documentation)
+ Data.Hashable: defaultHash :: Hashable a => a -> Int
+ Data.Hashable: defaultHashWithSalt :: Hashable a => Int -> a -> Int
Files
- CHANGES.md +6/−0
- hashable.cabal +6/−3
- src/Data/Hashable.hs +3/−0
- src/Data/Hashable/Class.hs +12/−14
- src/Data/Hashable/Imports.hs +2/−1
- src/Data/Hashable/LowLevel.hs +22/−2
- tests/Regress.hs +23/−7
CHANGES.md view
@@ -1,5 +1,11 @@ See also https://pvp.haskell.org/faq +## Version 1.4.3.0++ * Export `defaultHashWithSalt` and `defaultHash`.+ * Fix issue of tuples with 0 first component causing all-zero state.+ * Change `hashInt` to mix bits more.+ ## Version 1.4.2.0 * Fix the foreign signature of `getThreadId`
hashable.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: hashable-version: 1.4.2.0+version: 1.4.3.0 synopsis: A class for types that can be converted to a hash value description: This package defines a class, 'Hashable', for types that@@ -8,6 +8,8 @@ exists for the benefit of hashing-based data structures. The package provides instances for basic types and a way to combine hash values.+ .+ The 'Hashable' 'hash' values are not guaranteed to be stable across library versions, operating systems or architectures. For stable hashing use named hashes: SHA256, CRC32 etc. homepage: http://github.com/haskell-unordered-containers/hashable @@ -36,6 +38,7 @@ || ==9.0.2 || ==9.2.5 || ==9.4.4+ || ==9.6.1 extra-source-files: CHANGES.md@@ -75,7 +78,7 @@ include-dirs: include hs-source-dirs: src build-depends:- base >=4.10.1.0 && <4.18+ base >=4.10.1.0 && <4.19 , bytestring >=0.10.8.2 && <0.12 , containers >=0.5.10.2 && <0.7 , deepseq >=1.4.3.0 && <1.5@@ -84,7 +87,7 @@ , text >=1.2.3.0 && <1.3 || >=2.0 && <2.1 if !impl(ghc >=9.2)- build-depends: base-orphans >=0.8.6 && <0.9+ build-depends: base-orphans >=0.8.6 && <0.10 if !impl(ghc >=9.4) build-depends: data-array-byte >=0.1.0.1 && <0.2
src/Data/Hashable.hs view
@@ -60,6 +60,9 @@ , hashByteArray , hashByteArrayWithSalt + , defaultHashWithSalt+ , defaultHash+ -- * Caching hashes , Hashed , hashed
src/Data/Hashable/Class.hs view
@@ -50,6 +50,7 @@ , hashByteArray , hashByteArrayWithSalt , defaultHashWithSalt+ , defaultHash -- * Higher Rank Functions , hashWithSalt1 , hashWithSalt2@@ -228,7 +229,7 @@ -- Instances might want to implement this method to provide a more -- efficient implementation than the default implementation. hash :: a -> Int- hash = hashWithSalt defaultSalt+ hash = defaultHash default hashWithSalt :: (Generic a, GHashable Zero (Rep a)) => Int -> a -> Int hashWithSalt = genericHashWithSalt@@ -293,9 +294,19 @@ -- cannot also provide a default implementation for that method for -- the non-generic instance use case. Instead we provide -- 'defaultHashWith'.+--+-- @since 1.4.3.0+-- defaultHashWithSalt :: Hashable a => Int -> a -> Int defaultHashWithSalt salt x = salt `hashInt` hash x +-- | Default implementation of 'hash' based on 'hashWithSalt'.+--+-- @since 1.4.3.0+--+defaultHash :: Hashable a => a -> Int+defaultHash = hashWithSalt defaultSalt+ -- | Transform a value into a 'Hashable' value, then hash the -- transformed value using the given salt. --@@ -533,7 +544,6 @@ liftHashWithSalt2 _ h s (Right b) = s `hashInt` distinguisher `h` b instance (Hashable a1, Hashable a2) => Hashable (a1, a2) where- hash (a1, a2) = hash a1 `hashWithSalt` a2 hashWithSalt = hashWithSalt1 instance Hashable a1 => Hashable1 ((,) a1) where@@ -543,7 +553,6 @@ liftHashWithSalt2 h1 h2 s (a1, a2) = s `h1` a1 `h2` a2 instance (Hashable a1, Hashable a2, Hashable a3) => Hashable (a1, a2, a3) where- hash (a1, a2, a3) = hash a1 `hashWithSalt` a2 `hashWithSalt` a3 hashWithSalt = hashWithSalt1 instance (Hashable a1, Hashable a2) => Hashable1 ((,,) a1 a2) where@@ -555,8 +564,6 @@ instance (Hashable a1, Hashable a2, Hashable a3, Hashable a4) => Hashable (a1, a2, a3, a4) where- hash (a1, a2, a3, a4) = hash a1 `hashWithSalt` a2- `hashWithSalt` a3 `hashWithSalt` a4 hashWithSalt = hashWithSalt1 instance (Hashable a1, Hashable a2, Hashable a3) => Hashable1 ((,,,) a1 a2 a3) where@@ -568,9 +575,6 @@ instance (Hashable a1, Hashable a2, Hashable a3, Hashable a4, Hashable a5) => Hashable (a1, a2, a3, a4, a5) where- hash (a1, a2, a3, a4, a5) =- hash a1 `hashWithSalt` a2 `hashWithSalt` a3- `hashWithSalt` a4 `hashWithSalt` a5 hashWithSalt s (a1, a2, a3, a4, a5) = s `hashWithSalt` a1 `hashWithSalt` a2 `hashWithSalt` a3 `hashWithSalt` a4 `hashWithSalt` a5@@ -589,9 +593,6 @@ instance (Hashable a1, Hashable a2, Hashable a3, Hashable a4, Hashable a5, Hashable a6) => Hashable (a1, a2, a3, a4, a5, a6) where- hash (a1, a2, a3, a4, a5, a6) =- hash a1 `hashWithSalt` a2 `hashWithSalt` a3- `hashWithSalt` a4 `hashWithSalt` a5 `hashWithSalt` a6 hashWithSalt s (a1, a2, a3, a4, a5, a6) = s `hashWithSalt` a1 `hashWithSalt` a2 `hashWithSalt` a3 `hashWithSalt` a4 `hashWithSalt` a5 `hashWithSalt` a6@@ -611,9 +612,6 @@ instance (Hashable a1, Hashable a2, Hashable a3, Hashable a4, Hashable a5, Hashable a6, Hashable a7) => Hashable (a1, a2, a3, a4, a5, a6, a7) where- hash (a1, a2, a3, a4, a5, a6, a7) =- hash a1 `hashWithSalt` a2 `hashWithSalt` a3- `hashWithSalt` a4 `hashWithSalt` a5 `hashWithSalt` a6 `hashWithSalt` a7 hashWithSalt s (a1, a2, a3, a4, a5, a6, a7) = s `hashWithSalt` a1 `hashWithSalt` a2 `hashWithSalt` a3 `hashWithSalt` a4 `hashWithSalt` a5 `hashWithSalt` a6 `hashWithSalt` a7
src/Data/Hashable/Imports.hs view
@@ -5,9 +5,10 @@ Int64, Int32, Word64, Word32, xor, shiftR, shiftL,+ (.&.), ) where import Prelude () import Data.Int (Int64, Int32) import Data.Word (Word64, Word32)-import Data.Bits (xor, shiftR, shiftL)+import Data.Bits (xor, shiftR, shiftL, (.&.))
src/Data/Hashable/LowLevel.hs view
@@ -61,11 +61,31 @@ -- | Hash 'Int'. First argument is a salt, second argument is an 'Int'. -- The result is new salt / hash value. hashInt :: Salt -> Int -> Salt+hashInt s x = s `rnd` x1 `rnd` x2 `rnd` x3 `rnd` x4+ where+ {-# INLINE rnd #-}+ {-# INLINE x1 #-}+ {-# INLINE x2 #-}+ {-# INLINE x3 #-}+ {-# INLINE x4 #-} #if WORD_SIZE_IN_BITS == 64-hashInt s x = (s * 1099511628211) `xor` x+ -- See https://github.com/haskell-unordered-containers/hashable/issues/270+ -- FNV-1 is defined to hash byte at the time.+ -- We used to hash whole Int at once, which provided very bad mixing.+ -- Current is a performance-quality compromise, we do four rounds per Int (instead of 8 for FNV-1 or 1 for previous hashable).+ rnd a b = (a * 1099511628211) `xor` b+ x1 = shiftR x 48 .&. 0xffff+ x2 = shiftR x 32 .&. 0xffff+ x3 = shiftR x 16 .&. 0xffff+ x4 = x .&. 0xffff #else-hashInt s x = (s * 16777619) `xor` x+ rnd a b = (a * 16777619) `xor` b+ x1 = shiftR x 24 .&. 0xff+ x2 = shiftR x 16 .&. 0xff+ x3 = shiftR x 8 .&. 0xff+ x4 = x .&. 0xff #endif+ -- Note: FNV-1 hash takes a byte of data at once, here we take an 'Int', -- which is 4 or 8 bytes. Whether that's bad or not, I don't know.
tests/Regress.hs view
@@ -7,7 +7,8 @@ import qualified Test.Framework as F import Control.Monad (when) import Test.Framework.Providers.HUnit (testCase)-import Test.HUnit (assertFailure, (@=?))+import Test.HUnit (Assertion, assertFailure, (@?=))+import Test.Framework.Providers.QuickCheck2 (testProperty) import GHC.Generics (Generic) import Data.List (nub) import Data.Fixed (Pico)@@ -27,12 +28,17 @@ #include "MachDeps.h" +assertInequal :: Eq a => String -> a -> a -> Assertion+assertInequal msg x y+ | x == y = assertFailure msg+ | otherwise = return ()+ regressions :: [F.Test] regressions = [] ++ #ifdef HAVE_MMAP Mmap.regressions ++ [ testCase "Fixed" $ do- (hash (1 :: Pico) == hash (2 :: Pico)) @=? False+ (hash (1 :: Pico) == hash (2 :: Pico)) @?= False ] ++ #endif [ F.testGroup "Generic: sum of nullary constructors"@@ -42,17 +48,27 @@ , testCase "3" $ nullaryCase 3 S3 , testCase "4" $ nullaryCase 4 S4 ]++ , testCase "Zero tuples: issue 271" $ do+ assertInequal "Hash of (0,0) != 0" (hash (0 :: Int, 0 :: Int)) 0+ assertInequal "Hash of (0,0,0) != 0" (hash (0 :: Int, 0 :: Int, 0 :: Int)) 0++ , testProperty "odd, odd: issue 271" $ \x' y' ->+ let x = if odd x' then x' else x' + 1 :: Int+ y = if odd y' then y' else y' + 1 :: Int+ in hash (x, y) /= hash (negate x, negate y)+ , testCase "Generic: Peano https://github.com/tibbe/hashable/issues/135" $ do let ns = take 20 $ iterate S Z let hs = map hash ns- hs @=? nub hs+ hs @?= nub hs #if WORD_SIZE_IN_BITS == 64 , testCase "64 bit Text" $ do- hash ("hello world" :: Text) @=?+ hash ("hello world" :: Text) @?= #if MIN_VERSION_text(2,0,0)- 4078614214911247440+ 2589482369471999198 #else- -3875242662334356092+ -1955893671357159554 #endif #endif , F.testGroup "concatenation"@@ -103,7 +119,7 @@ let salt = 42 let expected = salt `hashWithSalt` n `hashWithSalt` () let actual = hashWithSalt salt s- expected @=? actual+ actual @?= expected data SumOfNullary = S0 | S1 | S2 | S3 | S4 deriving (Eq, Generic) instance Hashable SumOfNullary