on main help sign in
.github/workflows/ci.yml+3−4
since splitsnapshots
unifiedsplit
@@ -1,11 +1,10 @@
1name: CI
1name: CI
2
2
3+# Trigger the workflow on push or pull request, but only for the main branch
3on:
4on:
4− # Trigger the workflow on push or pull request,
5− # but only for the master branch
6 push:
5 push:
7 branches:
6 branches:
8− - master
7+ - main
9 pull_request:
8 pull_request:
10
9
11jobs:
10jobs:
@@ -24,7 +23,7 @@ jobs:
24
23
25 steps:
24 steps:
26 - uses: actions/checkout@v2
25 - uses: actions/checkout@v2
27− if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/master'
26+ if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/main'
28
27
29 - uses: actions/setup-haskell@v1
28 - uses: actions/setup-haskell@v1
30 name: Setup Haskell
29 name: Setup Haskell
CHANGELOG.md+1−1
@@ -53,4 +53,4 @@ The changelog is available [on GitHub][2].
53* Initially created.
53* Initially created.
54
54
55[1]: https://pvp.haskell.org
55[1]: https://pvp.haskell.org
56−[2]: https://github.com/kowainik/typerep-map/blob/master/CHANGELOG.md
56+[2]: https://github.com/kowainik/typerep-map/releases
README.md+1−1
@@ -4,7 +4,7 @@
4
4
5[![GitHub CI](https://github.com/kowainik/typerep-map/workflows/CI/badge.svg)](https://github.com/kowainik/typerep-map/actions)
5[![GitHub CI](https://github.com/kowainik/typerep-map/workflows/CI/badge.svg)](https://github.com/kowainik/typerep-map/actions)
6[![Build status](https://img.shields.io/travis/kowainik/typerep-map.svg?logo=travis)](https://travis-ci.org/kowainik/typerep-map)
6[![Build status](https://img.shields.io/travis/kowainik/typerep-map.svg?logo=travis)](https://travis-ci.org/kowainik/typerep-map)
7−[![Windows build status](https://ci.appveyor.com/api/projects/status/github/kowainik/typerep-map?branch=master&svg=true)](https://ci.appveyor.com/project/kowainik/typerep-map)
7+[![Windows build status](https://ci.appveyor.com/api/projects/status/github/kowainik/typerep-map?branch=main&svg=true)](https://ci.appveyor.com/project/kowainik/typerep-map)
8[![Hackage](https://img.shields.io/hackage/v/typerep-map.svg?logo=haskell)](https://hackage.haskell.org/package/typerep-map)
8[![Hackage](https://img.shields.io/hackage/v/typerep-map.svg?logo=haskell)](https://hackage.haskell.org/package/typerep-map)
9[![Stackage LTS](http://stackage.org/package/typerep-map/badge/lts)](http://stackage.org/lts/package/typerep-map)
9[![Stackage LTS](http://stackage.org/package/typerep-map/badge/lts)](http://stackage.org/lts/package/typerep-map)
10[![Stackage Nightly](http://stackage.org/package/typerep-map/badge/nightly)](http://stackage.org/nightly/package/typerep-map)
10[![Stackage Nightly](http://stackage.org/package/typerep-map/badge/nightly)](http://stackage.org/nightly/package/typerep-map)
src/Data/TMap.hs+33−1
@@ -37,6 +37,8 @@ module Data.TMap
37 , delete
37 , delete
38 , unionWith
38 , unionWith
39 , union
39 , union
40+ , intersectionWith
41+ , intersection
40 , map
42 , map
41 , adjust
43 , adjust
42 , alter
44 , alter
@@ -46,6 +48,8 @@ module Data.TMap
46 , member
48 , member
47 , size
49 , size
48 , keys
50 , keys
51+ , keysWith
52+ , toListWith
49 ) where
53 ) where
50
54
51import Prelude hiding (lookup, map)
55import Prelude hiding (lookup, map)
@@ -53,7 +57,7 @@ import Prelude hiding (lookup, map)
53import Data.Functor.Identity (Identity (..))
57import Data.Functor.Identity (Identity (..))
54import Data.Typeable (Typeable)
58import Data.Typeable (Typeable)
55import GHC.Exts (coerce)
59import GHC.Exts (coerce)
56−import Type.Reflection (SomeTypeRep)
60+import Type.Reflection (SomeTypeRep, TypeRep)
57
61
58import qualified Data.TypeRepMap as F
62import qualified Data.TypeRepMap as F
59
63
@@ -132,6 +136,24 @@ union :: TMap -> TMap -> TMap
132union = F.union
136union = F.union
133{-# INLINE union #-}
137{-# INLINE union #-}
134
138
139+-- | The intersection of two 'TMap's using a combining function.
140+--
141+-- @O(n + m)@
142+intersectionWith :: (forall x. Typeable x => x -> x -> x) -> TMap -> TMap -> TMap
143+intersectionWith f = F.intersectionWith fId
144+ where
145+ fId :: forall y . Typeable y => Identity y -> Identity y -> Identity y
146+ fId y1 y2 = f (coerce y1) (coerce y2)
147+{-# INLINE intersectionWith #-}
148+
149+-- | The intersection of two 'TMap's.
150+-- It keeps all values from the first map whose keys are present in the second.
151+--
152+-- @O(n + m)@
153+intersection :: TMap -> TMap -> TMap
154+intersection = F.intersection
155+{-# INLINE intersection #-}
156+
135{- | Lookup a value of the given type in a 'TMap'.
157{- | Lookup a value of the given type in a 'TMap'.
136
158
137>>> x = lookup $ insert (11 :: Int) empty
159>>> x = lookup $ insert (11 :: Int) empty
@@ -165,6 +187,16 @@ keys :: TMap -> [SomeTypeRep]
165keys = F.keys
187keys = F.keys
166{-# INLINE keys #-}
188{-# INLINE keys #-}
167
189
190+-- | Return the list of keys by wrapping them with a user-provided function.
191+keysWith :: (forall a. TypeRep a -> r) -> TMap -> [r]
192+keysWith = F.keysWith
193+{-# INLINE keysWith #-}
194+
195+-- | Return the list of key-value pairs by wrapping them with a user-provided function.
196+toListWith :: (forall a. Typeable a => a -> r) -> TMap -> [r]
197+toListWith = F.toListWith
198+{-# INLINE toListWith #-}
199+
168-- | Map a function over the values.
200-- | Map a function over the values.
169map :: (forall a. Typeable a => a -> a) -> TMap -> TMap
201map :: (forall a. Typeable a => a -> a) -> TMap -> TMap
170map f = F.hoistWithKey (liftToIdentity f)
202map f = F.hoistWithKey (liftToIdentity f)
src/Data/TypeRepMap.hs+5−1
@@ -5,7 +5,7 @@ Copyright: (c) 2017-2020 Kowainik
5SPDX-License-Identifier: MPL-2.0
5SPDX-License-Identifier: MPL-2.0
6Maintainer: Kowainik <xrom.xkov@gmail.com>
6Maintainer: Kowainik <xrom.xkov@gmail.com>
7
7
8− A version of 'Data.TMap.TMap' parametrized by an interpretation @f@. This
8+A version of 'Data.TMap.TMap' parametrized by an interpretation @f@. This
9sort of parametrization may be familiar to users of @vinyl@ records.
9sort of parametrization may be familiar to users of @vinyl@ records.
10
10
11@'TypeRepMap' f@ is a more efficient replacement for @DMap
11@'TypeRepMap' f@ is a more efficient replacement for @DMap
@@ -63,12 +63,16 @@ module Data.TypeRepMap
63 , hoistWithKey
63 , hoistWithKey
64 , unionWith
64 , unionWith
65 , union
65 , union
66+ , intersectionWith
67+ , intersection
66
68
67 -- * Query
69 -- * Query
68 , lookup
70 , lookup
69 , member
71 , member
70 , size
72 , size
71 , keys
73 , keys
74+ , keysWith
75+ , toListWith
72
76
73 -- * 'IsList'
77 -- * 'IsList'
74 , WrapTypeable (..)
78 , WrapTypeable (..)
src/Data/TypeRepMap/Internal.hs+59−8
@@ -205,6 +205,8 @@ insert x m
205-- inferred type variables (which are not subject to type applications).
205-- inferred type variables (which are not subject to type applications).
206type KindOf (a :: k) = k
206type KindOf (a :: k) = k
207
207
208+type ArgKindOf (f :: k -> l) = k
209+
208{- | Delete a value from a 'TypeRepMap'.
210{- | Delete a value from a 'TypeRepMap'.
209
211
210TypeRepMap optimizes for fast reads rather than modifications, as a trade-off deletes are
212TypeRepMap optimizes for fast reads rather than modifications, as a trade-off deletes are
@@ -350,6 +352,48 @@ union :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
350union = unionWith const
352union = unionWith const
351{-# INLINE union #-}
353{-# INLINE union #-}
352
354
355+-- | The 'intersection' of two 'TypeRepMap's using a combining function
356+--
357+-- @O(n + m)@
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)
361+ where
362+ f' :: forall x. TypeRep x -> f x -> f x -> f x
363+ f' tr = withTypeable tr f
364+
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)
367+
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.
371+ mergeMaps _ [] = []
372+ mergeMaps [] _ = []
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
378+ -- fingerprints
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 #-}
387+
388+-- | The intersection of two 'TypeRepMap's.
389+-- It keeps all values from the first map whose keys are present in the second.
390+--
391+-- @O(n + m)@
392+intersection :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
393+intersection = intersectionWith const
394+{-# INLINE intersection #-}
395+
396+
353{- | 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'.
354
398
355>>> member @Char $ one (Identity 'a')
399>>> member @Char $ one (Identity 'a')
@@ -385,9 +429,22 @@ size = sizeofPrimArray . fingerprintAs
385
429
386-- | Return the list of 'SomeTypeRep' from the keys.
430-- | Return the list of 'SomeTypeRep' from the keys.
387keys :: TypeRepMap f -> [SomeTypeRep]
431keys :: TypeRepMap f -> [SomeTypeRep]
388−keys TypeRepMap{..} = SomeTypeRep . anyToTypeRep <$> toList trKeys
432+keys = keysWith SomeTypeRep
389{-# INLINE keys #-}
433{-# INLINE keys #-}
390
434
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 #-}
439+
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
443+ where
444+ withTypeRep :: TypeRep a -> f a -> r
445+ withTypeRep tr an = withTypeable tr $ f an
446+ toF (_, an, k) = withTypeRep (unsafeCoerce k) (fromAny an)
447+
391-- | Binary searched based on this article
448-- | Binary searched based on this article
392-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
449-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
393-- with modification for our two-vector search case.
450-- with modification for our two-vector search case.
@@ -464,9 +521,6 @@ data WrapTypeable f where
464instance Show (WrapTypeable f) where
521instance Show (WrapTypeable f) where
465 show (WrapTypeable (_ :: f a)) = show $ calcFp @a
522 show (WrapTypeable (_ :: f a)) = show $ calcFp @a
466
523
467−wrapTypeable :: TypeRep a -> f a -> WrapTypeable f
468−wrapTypeable tr = withTypeable tr WrapTypeable
469−
470{- |
524{- |
471
525
472prop> fromList . toList == 'id'
526prop> fromList . toList == 'id'
@@ -494,10 +548,7 @@ instance IsList (TypeRepMap f) where
494 k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
548 k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
495
549
496 toList :: TypeRepMap f -> [WrapTypeable f]
550 toList :: TypeRepMap f -> [WrapTypeable f]
497− toList = map toWrapTypeable . toTriples
551+ toList = toListWith WrapTypeable
498− where
499− toWrapTypeable :: (Fingerprint, Any, Any) -> WrapTypeable f
500− toWrapTypeable (_, an, k) = wrapTypeable (unsafeCoerce k) (fromAny an)
501
552
502calcFp :: forall a . Typeable a => Fingerprint
553calcFp :: forall a . Typeable a => Fingerprint
503calcFp = typeRepFingerprint $ typeRep @a
554calcFp = typeRepFingerprint $ typeRep @a
test/Test/TypeRep/TypeRepMap.hs+62−4
@@ -5,14 +5,18 @@ module Test.TypeRep.TypeRepMap
5import Prelude hiding (lookup)
5import Prelude hiding (lookup)
6
6
7import Data.Functor.Identity (Identity (..))
7import Data.Functor.Identity (Identity (..))
8+import Data.List (sortBy)
9+import Data.Ord (comparing)
10+import Data.Typeable (cast)
8−import GHC.Exts (fromList)
11+import GHC.Exts (fromList, toList)
12+import Type.Reflection (SomeTypeRep(..), typeRep)
9−import Test.Hspec (Spec, describe, it, shouldBe)
13+import Test.Hspec (Spec, describe, it, shouldBe, shouldMatchList, shouldSatisfy, expectationFailure)
10
14
11−import Data.TMap (TMap, empty, insert, lookup, one, size, union)
15+import Data.TMap (TMap, empty, insert, lookup, one, size, union, keys)
12import Data.TypeRepMap.Internal (WrapTypeable (..))
16import Data.TypeRepMap.Internal (WrapTypeable (..))
13
17
14
18
15−-- Simple test for 'lookup', 'insert' and 'size' functions.
19+-- Simple test for 'lookup', 'insert', 'size', 'keys', 'toList' functions.
16typeRepMapSpec :: Spec
20typeRepMapSpec :: Spec
17typeRepMapSpec = describe "TypeRepMap" $ do
21typeRepMapSpec = describe "TypeRepMap" $ do
18 describe "Lookup Test" $ do
22 describe "Lookup Test" $ do
@@ -39,6 +43,60 @@ typeRepMapSpec = describe "TypeRepMap" $ do
39 lookup m `shouldBe` Just True
43 lookup m `shouldBe` Just True
40 lookup @Int m `shouldBe` Nothing
44 lookup @Int m `shouldBe` Nothing
41
45
46+ describe "Keys Test" $ do
47+ it "returns nothing on empty map" $
48+ keys empty `shouldBe` []
49+ it "returns the correct TypeRep" $
50+ keys (one 'a') `shouldBe` [SomeTypeRep $ typeRep @Char]
51+ it "returns the correct TypeReps for 10 different types" $
52+ keys mapOf10 `shouldMatchList`
53+ [ SomeTypeRep $ typeRep @Bool
54+ , SomeTypeRep $ typeRep @[Bool]
55+ , SomeTypeRep $ typeRep @(Maybe Bool)
56+ , SomeTypeRep $ typeRep @(Maybe ())
57+ , SomeTypeRep $ typeRep @[()]
58+ , SomeTypeRep $ typeRep @()
59+ , SomeTypeRep $ typeRep @String
60+ , SomeTypeRep $ typeRep @(Maybe Char)
61+ , SomeTypeRep $ typeRep @Char
62+ , SomeTypeRep $ typeRep @Int
63+ ]
64+
65+ describe "ToList Test" $ do
66+ it "returns nothing on empty map" $
67+ toList empty `shouldSatisfy` null
68+ it "returns correct result when 1 element is inserted" $
69+ case toList (one 'a') of
70+ [WrapTypeable (Identity x)] -> cast x `shouldBe` Just 'a'
71+ _ -> expectationFailure "did not return exactly 1 result"
72+ it "returns correct result when 10 elements are inserted" $ do
73+ let
74+ getTypeRep (WrapTypeable (Identity (_ :: a))) = SomeTypeRep $ typeRep @a
75+ got = sortBy (comparing getTypeRep) (toList mapOf10)
76+ expected = sortBy (comparing fst)
77+ [ (SomeTypeRep $ typeRep @Bool,
78+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just True)
79+ , (SomeTypeRep $ typeRep @[Bool],
80+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just [True, False])
81+ , (SomeTypeRep $ typeRep @(Maybe Bool),
82+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just (Just True))
83+ , (SomeTypeRep $ typeRep @(Maybe ()),
84+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just (Just ()))
85+ , (SomeTypeRep $ typeRep @[()],
86+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just [()])
87+ , (SomeTypeRep $ typeRep @(),
88+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just ())
89+ , (SomeTypeRep $ typeRep @String,
90+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just ("aaa" :: String))
91+ , (SomeTypeRep $ typeRep @(Maybe Char),
92+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just (Just 'a'))
93+ , (SomeTypeRep $ typeRep @Char,
94+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just 'a')
95+ , (SomeTypeRep $ typeRep @Int,
96+ \(WrapTypeable (Identity x)) -> cast x `shouldBe` Just (11 :: Int))
97+ ]
98+ length got `shouldBe` 10
99+ sequence_ $ zipWith snd expected got
42
100
43mapOf10 :: TMap
101mapOf10 :: TMap
44mapOf10 = insert True
102mapOf10 = insert True
test/Test/TypeRep/TypeRepMapProperty.hs+9−2
@@ -20,8 +20,8 @@ import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it)
20import Test.Hspec.Hedgehog (hedgehog)
20import Test.Hspec.Hedgehog (hedgehog)
21
21
22import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), delete, insert, invariantCheck,
22import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), delete, insert, invariantCheck,
23− lookup, member, generateOrderMapping, fromSortedList, alter,
23+ lookup, member, union, generateOrderMapping, fromSortedList,
24− adjust)
24+ adjust, alter, intersection)
25
25
26import qualified Hedgehog.Gen as Gen
26import qualified Hedgehog.Gen as Gen
27import qualified Hedgehog.Range as Range
27import qualified Hedgehog.Range as Range
@@ -39,6 +39,7 @@ typeRepMapPropertySpec = describe "TypeRepMap Property tests" $ do
39 alterDeleteSpec
39 alterDeleteSpec
40 alterAdjustSpec
40 alterAdjustSpec
41 alterModifySpec
41 alterModifySpec
42+ intersectionSpec
42 describe "Internal helpers" $ do
43 describe "Internal helpers" $ do
43 generateOrderMappingInvariantSpec
44 generateOrderMappingInvariantSpec
44 describe "Instance Laws" $ do
45 describe "Instance Laws" $ do
@@ -118,6 +119,12 @@ alterModifySpec = it "lookup k (alter f) == f (lookup k m)" $ hedgehog $ do
118 | otherwise = Just $ IntProxy p (n * 10)
119 | otherwise = Just $ IntProxy p (n * 10)
119 lookup @n @IntProxy (alter @n f m) === f (lookup @n @IntProxy m)
120 lookup @n @IntProxy (alter @n f m) === f (lookup @n @IntProxy m)
120
121
122+intersectionSpec :: Property
123+intersectionSpec = it "m `intersection` (m `union` n) == m" $ hedgehog $ do
124+ m <- forAll genMap
125+ n <- forAll genMap
126+ m `intersection` (m `union` n) === m
127+
121----------------------------------------------------------------------------
128----------------------------------------------------------------------------
122-- Internal helpers
129-- Internal helpers
123----------------------------------------------------------------------------
130----------------------------------------------------------------------------