hetero-dict-0.1.1.0: Data/Hetero/Dict.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
#if __GLASGOW_HASKELL__ < 710
{-# LANGUAGE OverlappingInstances #-}
#endif
-- | Fast read-only heterogeneous array.
--
-- This module is extracted from <http://hackage.haskell.org/package/web-routing web-routing>,
-- orginally desgined for high performance type safe routing.
-- The basic idea is:
--
-- 1. Construct a heterogeneous linked-list is O(n), since prepend is O(1).
--
-- 2. Convert it into a heterogeneous array in O(n).
--
-- 3. Following access will be a simple O(1) array indexing,
-- with index computed at compile time so you can't get missing keys.
--
-- In theory, it's faster than linked-list based data structures when n is large,
-- but it needs more benchmark to be sure.
--
-- Typical usage: a heterogeneous lookup table, indexed by type level string.
--
-- @
-- > :set -XDataKinds -XQuasiQuotes
-- > let d = mkDict . add [key|foo|] 12 . add [key|bar|] "baz" $ emptyStore
-- > get [key|foo|] d
-- 12
-- > get [key|bar|] d
-- "baz"
-- @
--
module Data.Hetero.Dict
(
-- ** Store
Store(..)
, emptyStore
, add
-- ** Dict
, Dict
, mkDict'
, mkDict
, InDict
, get
-- ** re-export from KVList
, key
, KV(..)
, KVList(..)
, NotHasKey
, Ix
-- ** helpers
, ShowDict(..)
) where
import qualified Control.Monad.Primitive as P
import Control.Monad.ST (ST, runST)
import Data.Hetero.KVList
import Data.List (intercalate)
import qualified Data.Primitive.Array as P
import Data.Proxy
import Data.Typeable (TypeRep, Typeable, typeOf)
import GHC.Exts (Any)
import GHC.TypeLits
import Unsafe.Coerce
--------------------------------------------------------------------------------
-- | Heterogeneous linked-list with a size field.
--
-- use 'mkDict' to convert it into a 'Dict'.
--
data Store kvs = Store
{ storeSize :: {-# UNPACK #-} !Int
, storeBody :: KVList kvs
}
-- | An empty 'Store'
--
emptyStore :: Store '[]
emptyStore = Store 0 Empty
{-# INLINE emptyStore #-}
-- | O(1) add key value pair to 'Store'.
--
-- @
-- > let a = add [key|foo|] (12 :: Int) emptyStore
-- > a
-- Store {foo = 12 :: Int}
-- > add [key|bar|] "baz" a
-- Store {bar = "baz" :: [Char], foo = 12 :: Int}
-- @
--
add :: (NotHasKey k kvs) => Proxy k -> v -> Store kvs -> Store (k ':= v ': kvs)
add _ v (Store l c) = Store (l + 1) (Cons v c)
{-# INLINE add #-}
--------------------------------------------------------------------------------
-- | Read-Only heterogeneous array.
--
-- The underline data structure is a boxed array,
-- support 'get' operation only.
newtype Dict (kvs :: [KV *]) = Dict (P.Array Any)
-- | O(n) convert a 'Store' into a 'Dict' inside 'ST' monad.
--
mkDict' :: forall s kvs. Store kvs -> ST s (Dict kvs)
mkDict' store = do
ary <- P.newArray (storeSize store) undefined
go ary
Dict `fmap` P.unsafeFreezeArray ary
where
go :: P.MutableArray (P.PrimState (ST s)) Any -> ST s ()
go array = loop 0 (storeBody store)
where
loop :: Int -> KVList kvs' -> ST s ()
loop !i (Cons v ss) = do
P.writeArray array i (unsafeCoerce v)
loop (i + 1) ss
loop _ Empty = return ()
{-# INLINABLE mkDict' #-}
-- | O(n) convert "Store" to 'Dict'.
mkDict :: Store kvs -> Dict kvs
mkDict store = runST $ mkDict' store
{-# INLINABLE mkDict #-}
getImpl :: forall i k kvs v. ('Index i ~ Ix k kvs, KnownNat i) => Proxy (k :: Symbol) -> Dict kvs -> v
getImpl _ (Dict d) = unsafeCoerce $ d `P.indexArray` fromIntegral (natVal (Proxy :: Proxy i))
{-# INLINABLE getImpl #-}
-- | Constraint ensure 'Dict' must contain k-v pair.
--
class InDict (k :: Symbol) (v :: *) (kvs :: [KV *]) | k kvs -> v where
get' :: Proxy k -> Dict kvs -> v
#if __GLASGOW_HASKELL__ >= 710
instance {-# OVERLAPPING #-} InDict k v (k ':= v ': kvs) where
#else
instance InDict k v (k ':= v ': kvs) where
#endif
get' = getImpl
{-# INLINE get' #-}
instance (InDict k v kvs, 'Index i ~ Ix k (k' ':= v' ': kvs), KnownNat i) => InDict k v (k' ':= v' ': kvs) where
get' = getImpl
{-# INLINE get' #-}
-- | O(1) get value using associated key from 'Dict'.
--
get :: InDict k v kvs => Proxy k -> Dict kvs -> v
get = get'
{-# INLINE get #-}
--------------------------------------------------------------------------------
-- | Helper class for defining store's 'Show' instance.
class ShowDict (kvs :: [KV *]) where
showDict :: Int -> Dict kvs -> [(String, String, TypeRep)]
instance ShowDict '[] where
showDict _ _ = []
instance (KnownSymbol k, Typeable v, Show v, ShowDict kvs) => ShowDict (k ':= v ': kvs) where
showDict i (Dict t) =
(symbolVal (Proxy :: Proxy k), show (unsafeCoerce $ P.indexArray t i :: v), typeOf (undefined :: v)):
showDict (i + 1) (unsafeCoerce $ Dict t :: Dict kvs)
instance ShowDict kvs => Show (Dict kvs) where
show d = "Dict {" ++
(intercalate ", " . map (\(k, v, t) -> k ++ " = " ++ v ++ " :: " ++ show t) $ showDict 0 d)
++ "}"
instance ShowDict kvs => Show (Store kvs) where
show d = "Store {" ++
(intercalate ", " . map (\(k, v, t) -> k ++ " = " ++ v ++ " :: " ++ show t) $ showDict 0 (mkDict d))
++ "}"