module Data.Interned.Internal
( Interned(..)
, Uninternable(..)
, mkCache
, Cache(..)
, CacheState(..)
, cacheSize
, Id
, intern
, recover
) where
import Data.Array
import Data.Hashable
import Data.HashMap.Strict (HashMap)
import Data.Foldable
import Data.Traversable
import qualified Data.HashMap.Strict as HashMap
import Data.IORef
import GHC.IO (unsafeDupablePerformIO, unsafePerformIO)
defaultCacheWidth :: Int
defaultCacheWidth = 1024
data CacheState t = CacheState
{ fresh :: !Id
, content :: !(HashMap (Description t) t)
}
newtype Cache t = Cache { getCache :: Array Int (IORef (CacheState t)) }
cacheSize :: Cache t -> IO Int
cacheSize (Cache t) = foldrM
(\a b -> do
v <- readIORef a
return $! HashMap.size (content v) + b
) 0 t
mkCache :: Interned t => Cache t
mkCache = result where
element = CacheState (seedIdentity result) HashMap.empty
w = cacheWidth result
result = Cache
$ unsafePerformIO
$ traverse newIORef
$ listArray (0,w 1)
$ replicate w element
type Id = Int
class ( Eq (Description t)
, Hashable (Description t)
) => Interned t where
data Description t
type Uninterned t
describe :: Uninterned t -> Description t
identify :: Id -> Uninterned t -> t
seedIdentity :: p t -> Id
seedIdentity _ = 0
cacheWidth :: p t -> Int
cacheWidth _ = defaultCacheWidth
modifyAdvice :: IO t -> IO t
modifyAdvice = id
cache :: Cache t
class Interned t => Uninternable t where
unintern :: t -> Uninterned t
intern :: Interned t => Uninterned t -> t
intern !bt = unsafeDupablePerformIO $ modifyAdvice $ atomicModifyIORef slot go
where
slot = getCache cache ! r
!dt = describe bt
!hdt = hash dt
!wid = cacheWidth dt
r = hdt `mod` wid
go (CacheState i m) = case HashMap.lookup dt m of
Nothing -> let t = identify (wid * i + r) bt in (CacheState (i + 1) (HashMap.insert dt t m), t)
Just t -> (CacheState i m, t)
recover :: Interned t => Description t -> IO (Maybe t)
recover !dt = do
CacheState _ m <- readIORef $ getCache cache ! (hash dt `mod` cacheWidth dt)
return $ HashMap.lookup dt m