1{-# LANGUAGE AllowAmbiguousTypes #-}
2{-# LANGUAGE BangPatterns #-}
3{-# LANGUAGE DataKinds #-}
5{-# LANGUAGE InstanceSigs #-}
6{-# LANGUAGE KindSignatures #-}
7{-# LANGUAGE MagicHash #-}
8{-# LANGUAGE PolyKinds #-}
9{-# LANGUAGE Rank2Types #-}
10{-# LANGUAGE TypeFamilies #-}
11{-# LANGUAGE TypeInType #-}
12{-# LANGUAGE ViewPatterns #-}
14-- {-# OPTIONS_GHC -ddump-simpl -dsuppress-idinfo -dsuppress-coercions -dsuppress-type-applications -dsuppress-uniques -dsuppress-module-prefixes #-}
16-- | Internal API for 'TypeRepMap' and operations on it. The functions here do
17-- not have any stability guarantees and can change between minor versions.
19-- If you need to use this module for purposes other than tests,
22module Data.TypeRepMap.Internal where
24import Prelude hiding (lookup)
26import Control.Monad.ST (runST)
27import Control.Monad.Zip (mzip)
28import Data.Function (on)
29import Data.IntMap.Strict (IntMap)
30import Data.Kind (Type)
31import Data.List (intercalate, nubBy)
32import Data.Maybe (fromJust)
33import Data.Primitive.Array (Array, freezeArray, indexArray, mapArray', readArray, sizeofArray,
34 thawArray, writeArray)
35import Data.Primitive.PrimArray (PrimArray, indexPrimArray, sizeofPrimArray)
36import Data.Semigroup (Semigroup (..))
37import GHC.Base (Any, Int (..), Int#, (*#), (+#), (<#))
38import GHC.Exts (IsList (..), inline, sortWith)
39import GHC.Fingerprint (Fingerprint (..))
40import GHC.Prim (eqWord#, ltWord#)
41import GHC.Word (Word64 (..))
42import Type.Reflection (SomeTypeRep (..), TypeRep, Typeable, typeRep, withTypeable)
43import Type.Reflection.Unsafe (typeRepFingerprint)
44import Unsafe.Coerce (unsafeCoerce)
46import qualified Data.IntMap.Strict as IM
47import qualified Data.Map.Strict as Map
48import qualified GHC.Exts as GHC (fromList, toList)
52'TypeRepMap' is a heterogeneous data structure similar in its essence to
53'Data.Map.Map' with types as keys, where each value has the type of its key. In
54addition to that, each value is wrapped in an interpretation @f@.
56Here is an example of using 'Prelude.Maybe' as an interpretation, with a
57comparison to 'Data.Map.Map':
60 'Data.Map.Map' 'Prelude.String' ('Prelude.Maybe' 'Prelude.String') 'TypeRepMap' 'Prelude.Maybe'
61--------------------------- ---------------------
62 \"Int\" -> Just \"5\" 'Prelude.Int' -> Just 5
63 \"Bool\" -> Just \"True\" 'Prelude.Bool' -> Just 'Prelude.True'
64 \"Char\" -> Nothing 'Prelude.Char' -> Nothing
67The runtime representation of 'TypeRepMap' is an array, not a tree. This makes
68'lookup' significantly more efficient.
71data TypeRepMap (f :: k -> Type) =
73 { fingerprintAs :: {-# UNPACK #-} !(PrimArray Word64) -- ^ first components of key fingerprints
74 , fingerprintBs :: {-# UNPACK #-} !(PrimArray Word64) -- ^ second components of key fingerprints
75 , trAnys :: {-# UNPACK #-} !(Array Any) -- ^ values stored in the map
76 , trKeys :: {-# UNPACK #-} !(Array Any) -- ^ typerep keys
78 -- ^ an unsafe constructor for 'TypeRepMap'
81instance Show (TypeRepMap f) where
82 show TypeRepMap{..} = "TypeRepMap [" ++ showKeys ++ "]"
85 showKeys = intercalate ", " $ toList $ mapArray' (show . anyToTypeRep) trKeys
87-- | Uses 'union' to combine 'TypeRepMap's.
88instance Semigroup (TypeRepMap f) where
89 (<>) :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
93instance Monoid (TypeRepMap f) where
94 mempty = TypeRepMap mempty mempty mempty mempty
97 {-# INLINE mappend #-}
99-- | Returns the list of 'Fingerprint's from 'TypeRepMap'.
100toFingerprints :: TypeRepMap f -> [Fingerprint]
101toFingerprints TypeRepMap{..} =
102 zipWith Fingerprint (GHC.toList fingerprintAs) (GHC.toList fingerprintBs)
106A 'TypeRepMap' with no values stored in it.
109prop> member @a empty == False
118Construct a 'TypeRepMap' with a single element.
120prop> size (one x) == 1
121prop> member @a (one (x :: f a)) == True
124one :: forall a f . Typeable a => f a -> TypeRepMap f
125one x = insert x empty
130Insert a value into a 'TypeRepMap'.
132prop> size (insert v tm) >= size tm
133prop> member @a (insert (x :: f a) tm) == True
136insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
137insert x = fromTriples . addX . toTriples
139 tripleX :: (Fingerprint, Any, Any)
140 tripleX@(fpX, _, _) = (calcFp @a, toAny x, unsafeCoerce $ typeRep @a)
142 addX :: [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)]
143 addX l = tripleX : deleteByFst fpX l
146-- Extract the kind of a type. We use it to work around lack of syntax for
147-- inferred type variables (which are not subject to type applications).
148type KindOf (a :: k) = k
150{- | Delete a value from a 'TypeRepMap'.
152prop> size (delete @a tm) <= size tm
153prop> member @a (delete @a tm) == False
155>>> tm = delete @Bool $ insert (Just True) $ one (Just 'a')
163delete :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> TypeRepMap f
164delete = fromTriples . deleteByFst (typeFp @a) . toTriples
168Update a value at a specific key with the result of the provided function. When
169the key is not a member of the map, the original map is returned.
171>>> trmap = fromList @(TypeRepMap Identity) [WrapTypeable $ Identity "a"]
172>>> lookup @String $ adjust (fmap (++ "ww")) trmap
175adjust :: forall a f . Typeable a => (f a -> f a) -> TypeRepMap f -> TypeRepMap f
176adjust fun tr = case cachedBinarySearch (typeFp @a) (fingerprintAs tr) (fingerprintBs tr) of
178 Just i -> tr {trAnys = changeAnyArr i (trAnys tr)}
180 changeAnyArr :: Int -> Array Any -> Array Any
181 changeAnyArr i trAs = runST $ do
182 let n = sizeofArray trAs
183 mutArr <- thawArray trAs 0 n
184 a <- toAny . fun . fromAny <$> readArray mutArr i
185 writeArray mutArr i a
186 freezeArray mutArr 0 n
189{- | Map over the elements of a 'TypeRepMap'.
191>>> tm = insert (Identity True) $ one (Identity 'a')
196>>> tm2 = hoist ((:[]) . runIdentity) tm
202hoist :: (forall x. f x -> g x) -> TypeRepMap f -> TypeRepMap g
203hoist f (TypeRepMap as bs ans ks) = TypeRepMap as bs (mapArray' (toAny . f . fromAny) ans) ks
206hoistA :: (Applicative t) => (forall x. f x -> t (g x)) -> TypeRepMap f -> t (TypeRepMap g)
207hoistA f (TypeRepMap as bs (toList -> ans) ks) = (\l -> TypeRepMap as bs (fromList $ map toAny l) ks)
208 <$> traverse (f . fromAny) ans
211hoistWithKey :: forall f g. (forall x. Typeable x => f x -> g x) -> TypeRepMap f -> TypeRepMap g
212hoistWithKey f (TypeRepMap as bs ans ks) = TypeRepMap as bs newAns ks
214 newAns = mapArray' mapAns (mzip ans ks)
215 mapAns (a, k) = toAny $ withTr (unsafeCoerce k) $ fromAny a
217 withTr :: forall x. TypeRep x -> f x -> g x
218 withTr t = withTypeable t f
219{-# INLINE hoistWithKey #-}
221-- | The union of two 'TypeRepMap's using a combining function.
222unionWith :: (forall x. f x -> f x -> f x) -> TypeRepMap f -> TypeRepMap f -> TypeRepMap f
223unionWith f m1 m2 = fromTriples
225 $ Map.unionWith combine
226 (fromTripleList $ toTriples m1)
227 (fromTripleList $ toTriples m2)
229 combine :: (Any, Any) -> (Any, Any) -> (Any, Any)
230 combine (av, ak) (bv, _) = (toAny $ f (fromAny av) (fromAny bv), ak)
232 fromTripleList :: Ord a => [(a, b, c)] -> Map.Map a (b, c)
233 fromTripleList = Map.fromList . map (\(a, b, c) -> (a, (b, c)))
235 toTripleList :: Map.Map a (b, c) -> [(a, b, c)]
236 toTripleList = map (\(a, (b, c)) -> (a, b, c)) . Map.toList
237{-# INLINE unionWith #-}
239-- | The (left-biased) union of two 'TypeRepMap's. It prefers the first map when
240-- duplicate keys are encountered, i.e. @'union' == 'unionWith' const@.
241union :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
242union = unionWith const
245{- | Check if a value of the given type is present in a 'TypeRepMap'.
247>>> member @Char $ one (Identity 'a')
249>>> member @Bool $ one (Identity 'a')
252member :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> Bool
253member tm = case lookup @a tm of
258{- | Lookup a value of the given type in a 'TypeRepMap'.
260>>> x = lookup $ insert (Identity (11 :: Int)) empty
261>>> x :: Maybe (Identity Int)
263>>> x :: Maybe (Identity ())
266lookup :: forall a f . Typeable a => TypeRepMap f -> Maybe (f a)
267lookup tVect = fromAny . (trAnys tVect `indexArray`)
268 <$> cachedBinarySearch (typeFp @a)
269 (fingerprintAs tVect)
270 (fingerprintBs tVect)
273-- | Get the amount of elements in a 'TypeRepMap'.
274size :: TypeRepMap f -> Int
275size = sizeofPrimArray . fingerprintAs
278-- | Return the list of 'SomeTypeRep' from the keys.
279keys :: TypeRepMap f -> [SomeTypeRep]
280keys TypeRepMap{..} = SomeTypeRep . anyToTypeRep <$> toList trKeys
283-- | Binary searched based on this article
284-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
285-- with modification for our two-vector search case.
286cachedBinarySearch :: Fingerprint -> PrimArray Word64 -> PrimArray Word64 -> Maybe Int
287cachedBinarySearch (Fingerprint (W64# a) (W64# b)) fpAs fpBs = inline (go 0#)
289 go :: Int# -> Maybe Int
290 go i = case i <# len of
292 _ -> let !(W64# valA) = indexPrimArray fpAs (I# i) in case a `ltWord#` valA of
293 0# -> case a `eqWord#` valA of
294 0# -> go (2# *# i +# 2#)
295 _ -> let !(W64# valB) = indexPrimArray fpBs (I# i) in case b `eqWord#` valB of
296 0# -> case b `ltWord#` valB of
297 0# -> go (2# *# i +# 2#)
298 _ -> go (2# *# i +# 1#)
300 _ -> go (2# *# i +# 1#)
303 len = let !(I# l) = sizeofPrimArray fpAs in l
304{-# INLINE cachedBinarySearch #-}
306----------------------------------------------------------------------------
308----------------------------------------------------------------------------
314fromAny = unsafeCoerce
316anyToTypeRep :: Any -> TypeRep f
317anyToTypeRep = unsafeCoerce
319typeFp :: forall a . Typeable a => Fingerprint
320typeFp = typeRepFingerprint $ typeRep @a
323toTriples :: TypeRepMap f -> [(Fingerprint, Any, Any)]
324toTriples tm = zip3 (toFingerprints tm) (GHC.toList $ trAnys tm) (GHC.toList $ trKeys tm)
326deleteByFst :: Eq a => a -> [(a, b, c)] -> [(a, b, c)]
327deleteByFst x = filter ((/= x) . fst3)
329nubByFst :: (Eq a) => [(a, b, c)] -> [(a, b, c)]
330nubByFst = nubBy ((==) `on` fst3)
332fst3 :: (a, b, c) -> a
335----------------------------------------------------------------------------
336-- Functions for testing and benchmarking
337----------------------------------------------------------------------------
339-- | Existential wrapper around 'Typeable' indexed by @f@ type parameter.
340-- Useful for 'TypeRepMap' structure creation form list of 'WrapTypeable's.
341data WrapTypeable f where
342 WrapTypeable :: Typeable a => f a -> WrapTypeable f
344instance Show (WrapTypeable f) where
345 show (WrapTypeable (_ :: f a)) = show $ calcFp @a
349prop> fromList . toList == 'id'
351Creates 'TypeRepMap' from a list of 'WrapTypeable's.
353>>> show $ fromList [WrapTypeable $ Identity True, WrapTypeable $ Identity 'a']
354TypeRepMap [Bool, Char]
358instance IsList (TypeRepMap f) where
359 type Item (TypeRepMap f) = WrapTypeable f
361 fromList :: [WrapTypeable f] -> TypeRepMap f
362 fromList = fromTriples . map (\x -> (fp x, an x, k x))
364 fp :: WrapTypeable f -> Fingerprint
365 fp (WrapTypeable (_ :: f a)) = calcFp @a
367 an :: WrapTypeable f -> Any
368 an (WrapTypeable x) = toAny x
370 k :: WrapTypeable f -> Any
371 k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
373 toList :: TypeRepMap f -> [WrapTypeable f]
374 toList = map toWrapTypeable . toTriples
376 toWrapTypeable :: (Fingerprint, Any, Any) -> WrapTypeable f
377 toWrapTypeable (_, an, k) = withTypeable (unsafeCoerce k) $ fromAny an
379calcFp :: forall a . Typeable a => Fingerprint
380calcFp = typeRepFingerprint $ typeRep @a
382fromTriples :: [(Fingerprint, Any, Any)] -> TypeRepMap f
383fromTriples kvs = TypeRepMap (GHC.fromList fpAs) (GHC.fromList fpBs) (GHC.fromList ans) (GHC.fromList ks)
385 (fpAs, fpBs) = unzip $ map (\(Fingerprint a b) -> (a, b)) fps
386 (fps, ans, ks) = unzip3 $ fromSortedList $ sortWith fst3 $ nubByFst kvs
388----------------------------------------------------------------------------
389-- Tree-like conversion
390----------------------------------------------------------------------------
392fromSortedList :: forall a . [a] -> [a]
393fromSortedList l = IM.elems $ fst $ go 0 0 mempty (IM.fromList $ zip [0..] l)
395 -- state monad could be used here, but it's another dependency
396 go :: Int -> Int -> IntMap a -> IntMap a -> (IntMap a, Int)
397 go i first result vector =
398 if i >= IM.size vector
401 let (newResult, newFirst) = go (2 * i + 1) first result vector
402 let withCur = IM.insert i (fromJust $ IM.lookup newFirst vector) newResult
403 go (2 * i + 2) (newFirst + 1) withCur vector
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list