@@ -45,7 +45,7 @@ import Data.Kind (Type)
45import Data.Type.Equality ((:~:) (..), TestEquality (..))
45import Data.Type.Equality ((:~:) (..), TestEquality (..))
46import Data.List (intercalate, nubBy)
46import Data.List (intercalate, nubBy)
47import Data.Maybe (fromMaybe)
47import Data.Maybe (fromMaybe)
48−import Data.Primitive.Array (Array, MutableArray, indexArray, mapArray', readArray, sizeofArray,
48+import Data.Primitive.Array (Array, MutableArray, indexArray, mapArray', sizeofArray,
49 thawArray, unsafeFreezeArray, writeArray)
49 thawArray, unsafeFreezeArray, writeArray)
50import Data.Primitive.PrimArray (writePrimArray, newPrimArray, unsafeFreezePrimArray, primArrayToList, MutablePrimArray, primArrayFromListN, PrimArray, indexPrimArray, sizeofPrimArray)
50import Data.Primitive.PrimArray (writePrimArray, newPrimArray, unsafeFreezePrimArray, primArrayToList, MutablePrimArray, primArrayFromListN, PrimArray, indexPrimArray, sizeofPrimArray)
51import Data.Semigroup (Semigroup (..), All(..))
51import Data.Semigroup (Semigroup (..), All(..))
@@ -180,27 +180,38 @@ one x = TypeRepMap (primArrayFromListN 1 [fa])
182Insert a value into a 'TypeRepMap'.
182Insert a value into a 'TypeRepMap'.
183+TypeRepMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
184prop> size (insert v tm) >= size tm
185prop> size (insert v tm) >= size tm
185prop> member @a (insert (x :: f a) tm) == True
186prop> member @a (insert (x :: f a) tm) == True
188insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
189insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
189−insert x = fromTriples . addX . toTriples
190+insert x m
191+ | size m == 0 = one x
192+ | otherwise = case cachedBinarySearch (typeFp @a) (fingerprintAs m) (fingerprintBs m) of
193+ Nothing -> union m $ one x
194+ Just i -> m {trAnys = changeAnyArr i (trAnys m)}
191− tripleX :: (Fingerprint, Any, Any)
196+ changeAnyArr :: Int -> Array Any -> Array Any
192− tripleX@(fpX, _, _) = (calcFp @a, toAny x, unsafeCoerce $ typeRep @a)
197+ changeAnyArr i trAs = runST $ do
193−
198+ let n = sizeofArray trAs
194− addX :: [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)]
199+ mutArr <- thawArray trAs 0 n
195− addX l = tripleX : deleteByFst fpX l
200+ writeArray mutArr i $ toAny x
201+ unsafeFreezeArray mutArr
196{-# INLINE insert #-}
202{-# INLINE insert #-}
198-- Extract the kind of a type. We use it to work around lack of syntax for
204-- Extract the kind of a type. We use it to work around lack of syntax for
199-- inferred type variables (which are not subject to type applications).
205-- inferred type variables (which are not subject to type applications).
200type KindOf (a :: k) = k
206type KindOf (a :: k) = k
208+type ArgKindOf (f :: k -> l) = k
202{- | Delete a value from a 'TypeRepMap'.
210{- | Delete a value from a 'TypeRepMap'.
212+TypeRepMap optimizes for fast reads rather than modifications, as a trade-off deletes are
213+@O(n)@, with an @O(log(n))@ optimization for when the element is already missing.
204prop> size (delete @a tm) <= size tm
215prop> size (delete @a tm) <= size tm
205prop> member @a (delete @a tm) == False
216prop> member @a (delete @a tm) == False
@@ -213,30 +224,60 @@ False
215delete :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> TypeRepMap f
226delete :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> TypeRepMap f
216−delete = fromTriples . deleteByFst (typeFp @a) . toTriples
227+delete m
228+ -- Lookups are fast, so check if we even have the element first.
229+ | not (member @a m) = m
230+ -- We know we have the element, If the map has exactly one element, we can return the empty map
231+ | size m == 1 = empty
232+ -- Otherwise, filter out the element in linear time.
233+ | otherwise = fromSortedTriples . deleteFirst ((== typeFp @a) . fst3) . toSortedTriples $ m
217{-# INLINE delete #-}
234{-# INLINE delete #-}
236+deleteFirst :: (a -> Bool) -> [a] -> [a]
237+deleteFirst _ [] = []
238+deleteFirst p (x : xs) = if p x then xs else x : deleteFirst p xs
220−Update a value at a specific key with the result of the provided function. When
241+Update a value at a specific key with the result of the provided function.
221−the key is not a member of the map, the original map is returned.
242+When the key is not a member of the map, the original map is returned.
223>>> trmap = fromList @(TypeRepMap Identity) [WrapTypeable $ Identity "a"]
244>>> trmap = fromList @(TypeRepMap Identity) [WrapTypeable $ Identity "a"]
224>>> lookup @String $ adjust (fmap (++ "ww")) trmap
245>>> lookup @String $ adjust (fmap (++ "ww")) trmap
225Just (Identity "aww")
246Just (Identity "aww")
227adjust :: forall a f . Typeable a => (f a -> f a) -> TypeRepMap f -> TypeRepMap f
248adjust :: forall a f . Typeable a => (f a -> f a) -> TypeRepMap f -> TypeRepMap f
249+adjust fun = alter (fmap fun)
250+{-# INLINE adjust #-}
253+Updates a value at a specific key, whether or not it exists.
254+This can be used to insert, delete, or update a value of a given type in the map.
256+>>> func = (\case Nothing -> Just (Identity "new"); Just (Identity s) -> Just (Identity (reverse s)))
257+>>> lookup @String $ alter func empty
258+Just (Identity "new")
259+>>> trmap = fromList @(TypeRepMap Identity) [WrapTypeable $ Identity "helllo"]
260+>>> lookup @String $ alter func trmap
261+>>> Just (Identity "olleh")
263+alter :: forall a f . Typeable a => (Maybe (f a) -> Maybe (f a)) -> TypeRepMap f -> TypeRepMap f
228−adjust fun tr = case cachedBinarySearch (typeFp @a) (fingerprintAs tr) (fingerprintBs tr) of
264+alter fun tr = case cachedBinarySearch (typeFp @a) (fingerprintAs tr) (fingerprintBs tr) of
229− Nothing -> tr
265+ Nothing ->
266+ case (fun Nothing) of
268+ Just v -> insert v tr
270+ case fun (Just . fromAny $ indexArray (trAnys tr) i) of
271+ Nothing -> delete @a tr
230− Just i -> tr {trAnys = changeAnyArr i (trAnys tr)}
272+ Just v -> tr{trAnys = replaceAnyAt i (toAny v) (trAnys tr)}
232− changeAnyArr :: Int -> Array Any -> Array Any
274+ replaceAnyAt :: Int -> Any -> Array Any -> Array Any
233− changeAnyArr i trAs = runST $ do
275+ replaceAnyAt i v trAs = runST $ do
234 let n = sizeofArray trAs
276 let n = sizeofArray trAs
235 mutArr <- thawArray trAs 0 n
277 mutArr <- thawArray trAs 0 n
236− a <- toAny . fun . fromAny <$> readArray mutArr i
237− writeArray mutArr i a
278+ writeArray mutArr i v
238 unsafeFreezeArray mutArr
279 unsafeFreezeArray mutArr
239−{-# INLINE adjust #-}
280+{-# INLINE alter #-}
241{- | Map over the elements of a 'TypeRepMap'.
282{- | Map over the elements of a 'TypeRepMap'.
@@ -311,6 +352,48 @@ union :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
311union = unionWith const
352union = unionWith const
312{-# INLINE union #-}
353{-# INLINE union #-}
355+-- | The 'intersection' of two 'TypeRepMap's using a combining function
358+intersectionWith :: forall f. (forall x. Typeable x => f x -> f x -> f x) -> TypeRepMap f -> TypeRepMap f -> TypeRepMap f
359+intersectionWith f ma mb =
360+ fromSortedTriples $ mergeMaps (toSortedTriples ma) (toSortedTriples mb)
362+ f' :: forall x. TypeRep x -> f x -> f x -> f x
363+ f' tr = withTypeable tr f
365+ combine :: (Fingerprint, Any, Any) -> (Fingerprint, Any, Any) -> (Fingerprint, Any, Any)
366+ combine (fp, av, ak) (_, bv, _) = (fp, toAny $ f' (fromAny ak) (fromAny av) (fromAny bv), ak)
368+ -- Merges two typrepmaps into a sorted, dedup'd list of triples.
369+ mergeMaps :: [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)]
370+ -- If either list is empty, the intersection must be finished.
373+ -- Merge the two maps considering one element at a time.
374+ mergeMaps (a@(af, _, _) : as) (b@(bf, _, _) : bs) =
375+ case compare af bf of
376+ -- Fingerprints are equal, union the elements using our function
377+ -- If the incoming maps were de-duped, there shouldn't be any other equivalent
379+ EQ -> combine a b : mergeMaps as bs
380+ -- First fingerprint must not be in the second map or we would have seen it by now
381+ -- Skip it an move on
382+ LT -> mergeMaps as (b : bs)
383+ -- Second fingerprint must not be in the first map or we would have seen it by now
384+ -- Skip it an move on
385+ GT -> mergeMaps (a:as) bs
386+{-# INLINE intersectionWith #-}
388+-- | The intersection of two 'TypeRepMap's.
389+-- It keeps all values from the first map whose keys are present in the second.
392+intersection :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
393+intersection = intersectionWith const
394+{-# INLINE intersection #-}
314{- | Check if a value of the given type is present in a 'TypeRepMap'.
397{- | Check if a value of the given type is present in a 'TypeRepMap'.
316>>> member @Char $ one (Identity 'a')
399>>> member @Char $ one (Identity 'a')
@@ -346,9 +429,22 @@ size = sizeofPrimArray . fingerprintAs
347-- | Return the list of 'SomeTypeRep' from the keys.
430-- | Return the list of 'SomeTypeRep' from the keys.
348keys :: TypeRepMap f -> [SomeTypeRep]
431keys :: TypeRepMap f -> [SomeTypeRep]
349−keys TypeRepMap{..} = SomeTypeRep . anyToTypeRep <$> toList trKeys
432+keys = keysWith SomeTypeRep
350{-# INLINE keys #-}
433{-# INLINE keys #-}
435+-- | Return the list of keys by wrapping them with a user-provided function.
436+keysWith :: (forall (a :: ArgKindOf f). TypeRep a -> r) -> TypeRepMap f -> [r]
437+keysWith f TypeRepMap{..} = f . anyToTypeRep <$> toList trKeys
438+{-# INLINE keysWith #-}
440+-- | Return the list of key-value pairs by wrapping them with a user-provided function.
441+toListWith :: forall f r. (forall (a :: ArgKindOf f). Typeable a => f a -> r) -> TypeRepMap f -> [r]
442+toListWith f = map toF . toTriples
444+ withTypeRep :: TypeRep a -> f a -> r
445+ withTypeRep tr an = withTypeable tr $ f an
446+ toF (_, an, k) = withTypeRep (unsafeCoerce k) (fromAny an)
352-- | Binary searched based on this article
448-- | Binary searched based on this article
353-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
449-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
354-- with modification for our two-vector search case.
450-- with modification for our two-vector search case.
@@ -407,9 +503,6 @@ toSortedTriples tm = trip <$> ordering
407 ordering :: [ Int ]
503 ordering :: [ Int ]
408 ordering = generateOrderMapping (size tm)
504 ordering = generateOrderMapping (size tm)
410−deleteByFst :: Eq a => a -> [(a, b, c)] -> [(a, b, c)]
411−deleteByFst x = filter ((/= x) . fst3)
413nubByFst :: (Eq a) => [(a, b, c)] -> [(a, b, c)]
506nubByFst :: (Eq a) => [(a, b, c)] -> [(a, b, c)]
414nubByFst = nubBy ((==) `on` fst3)
507nubByFst = nubBy ((==) `on` fst3)
@@ -428,9 +521,6 @@ data WrapTypeable f where
428instance Show (WrapTypeable f) where
521instance Show (WrapTypeable f) where
429 show (WrapTypeable (_ :: f a)) = show $ calcFp @a
522 show (WrapTypeable (_ :: f a)) = show $ calcFp @a
431−wrapTypeable :: TypeRep a -> f a -> WrapTypeable f
432−wrapTypeable tr = withTypeable tr WrapTypeable
436prop> fromList . toList == 'id'
526prop> fromList . toList == 'id'
@@ -458,10 +548,7 @@ instance IsList (TypeRepMap f) where
458 k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
548 k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
460 toList :: TypeRepMap f -> [WrapTypeable f]
550 toList :: TypeRepMap f -> [WrapTypeable f]
461− toList = map toWrapTypeable . toTriples
551+ toList = toListWith WrapTypeable
463− toWrapTypeable :: (Fingerprint, Any, Any) -> WrapTypeable f
464− toWrapTypeable (_, an, k) = wrapTypeable (unsafeCoerce k) (fromAny an)
466calcFp :: forall a . Typeable a => Fingerprint
553calcFp :: forall a . Typeable a => Fingerprint
467calcFp = typeRepFingerprint $ typeRep @a
554calcFp = typeRepFingerprint $ typeRep @a