hash-cons-0.1.0.0: src/Data/HashCons/Internal.hs
{-# LANGUAGE MagicHash #-}
module Data.HashCons.Internal where
import Control.Monad (when)
import Data.Hashable (Hashable, hash, hashWithSalt)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import System.IO.Unsafe (unsafeDupablePerformIO)
import Text.ParserCombinators.ReadPrec (step)
import Text.Read (Read(..), lexP, parens, prec)
import Text.Read.Lex (Lexeme (Ident))
import GHC.Exts (reallyUnsafePtrEquality#)
-- | 'HashCons' with a precomputed hash and an 'IORef' to the value.
--
-- WARNING: Do not use this type to wrap types whose Eq or Ord instances
-- allow distinguishable values to compare as equal; this will result in
-- nondeterminism or even visible mutation of semantically-immutable
-- values at runtime.
data HashCons a = HashCons
{ _hashCons_hash :: {-# UNPACK #-} !Int -- ^ Precomputed hash
, _hashCons_ref :: {-# UNPACK #-} !(IORef a) -- ^ Reference to the value
}
-- | Create a new 'HashCons'.
hashCons :: Hashable a => a -> HashCons a
hashCons a = HashCons (hash a) $ unsafeDupablePerformIO $ newIORef a
-- | Extract the value from a 'HashCons'.
unHashCons :: HashCons a -> a
unHashCons (HashCons _ ref) = unsafeDupablePerformIO $ readIORef ref
-- | Show instance that displays 'HashCons' in the format "hashCons <x>"
instance Show a => Show (HashCons a) where
showsPrec d hc = showParen (d > appPrec) $
showString "hashCons " . showsPrec (appPrec + 1) (unHashCons hc)
where
appPrec = 10
-- | Read instance that parses 'HashCons' from the format "hashCons <x>"
instance (Read a, Hashable a) => Read (HashCons a) where
readPrec = parens $ prec 10 $ do
Ident "hashCons" <- lexP
a <- step readPrec
pure $ hashCons a
instance Eq a => Eq (HashCons a) where
HashCons h1 ref1 == HashCons h2 ref2
| ref1 == ref2 = True
| h1 /= h2 = False
| otherwise = unsafeDupablePerformIO $ do
a1 <- readIORef ref1
a2 <- readIORef ref2
let eq = a1 == a2
when eq $ case reallyUnsafePtrEquality# a1 a2 of
0# -> writeIORef ref1 a2
_ -> pure ()
pure eq
-- | NOTE: This instance orders by hash first, and only secondarily by
-- the 'Ord' instance of 'a', to improve performance.
instance Ord a => Ord (HashCons a) where
compare (HashCons h1 ref1) (HashCons h2 ref2) = case compare h1 h2 of
EQ -> if ref1 == ref2
then EQ
else unsafeDupablePerformIO $ do
a1 <- readIORef ref1
a2 <- readIORef ref2
let result = compare a1 a2
when (result == EQ) $ case reallyUnsafePtrEquality# a1 a2 of
0# -> writeIORef ref1 a2
_ -> pure ()
pure result
result -> result
instance Eq a => Hashable (HashCons a) where
hashWithSalt salt (HashCons h _) = hashWithSalt salt h