on main help sign in
21 files changed+757−395
since splitsnapshots
unifiedsplit
.github/workflows/ci.yml+3−4 .travis.yml+10−28 CHANGELOG.md+1−1 README.md+6−2 appveyor.yml+35 benchmark/CMap.hs+46−39 benchmark/CacheMap.hs+48−45 benchmark/DMap.hs+45−42 benchmark/Main.hs+37−32 benchmark/OptimalVector.hs+29−28 benchmark/Spec.hs+61−50 benchmark/Vector.hs+23−22 src/Data/TMap.hs+48−2 src/Data/TypeRepMap.hs+6−1 src/Data/TypeRepMap/Internal.hs+198−50 test/Test/TypeRep/TypeRepMap.hs+62−4 test/Test/TypeRep/TypeRepMapProperty.hs+70−29 typerep-extra-impls/Data/TypeRep/CMap.hs+3−3 typerep-extra-impls/Data/TypeRep/OptimalVector.hs+7−8 typerep-extra-impls/Data/TypeRep/Vector.hs+2−2 typerep-map.cabal+17−3
.github/workflows/ci.yml+3−4
@@ -1,11 +1,10 @@
11name: CI
22
3+# Trigger the workflow on push or pull request, but only for the main branch
34on:
4− # Trigger the workflow on push or pull request,
5− # but only for the master branch
65 push:
76 branches:
8− - master
7+ - main
98 pull_request:
109
1110jobs:
@@ -24,7 +23,7 @@ jobs:
2423
2524 steps:
2625 - 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'
2827
2928 - uses: actions/setup-haskell@v1
3029 name: Setup Haskell
.travis.yml+10−28
@@ -1,46 +1,28 @@
11sudo: true
2−language: haskell
2+language: generic
33
44git:
55 depth: 5
66
7−cabal: "3.0"
8−
97cache:
108 directories:
11− - "$HOME/.cabal/store"
129 - "$HOME/.stack"
13− - "$TRAVIS_BUILD_DIR/.stack-work"
1410
1511matrix:
1612 include:
17− - ghc: 8.2.2
18− - ghc: 8.4.4
19− - ghc: 8.6.5
20− - ghc: 8.8.3
21− - ghc: 8.10.1
22−
23− - ghc: 8.8.3
24− env: STACK_YAML="$TRAVIS_BUILD_DIR/stack.yaml"
13+ - env: GHC='8.8.3'
14+ os: linux
15+ - env: GHC='8.8.3'
16+ os: osx
2517
2618install:
27− - |
28− if [ -z "$STACK_YAML" ]; then
29− cabal update
30− cabal build --enable-tests --enable-benchmarks
31− else
32− curl -sSL https://get.haskellstack.org/ | sh
33− stack --version
34− stack build --system-ghc --test --no-run-tests
35− fi
19+ # install stack and build project
20+ - curl -sSL https://get.haskellstack.org/ | sh
21+ - stack --version
22+ - stack build --test --no-run-tests --bench --no-run-benchmarks
3623
3724script:
38− - |
39− if [ -z "$STACK_YAML" ]; then
40− cabal test --enable-tests
41− else
42− stack build --system-ghc --test --bench --no-run-benchmarks --no-terminal --ghc-options=-Werror
43− fi
25+ - stack test --no-terminal
4426
4527notifications:
4628 email: false
CHANGELOG.md+1−1
@@ -53,4 +53,4 @@ The changelog is available [on GitHub][2].
5353* Initially created.
5454
5555[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+6−2
@@ -1,11 +1,15 @@
11# typerep-map
22
3−![electricity](https://user-images.githubusercontent.com/8126674/44323413-788dd700-a484-11e8-842e-f224cfaa4206.png)
3+![logo](https://user-images.githubusercontent.com/8126674/44323413-788dd700-a484-11e8-842e-f224cfaa4206.png)
4+
5+[![GitHub CI](https://github.com/kowainik/typerep-map/workflows/CI/badge.svg)](https://github.com/kowainik/typerep-map/actions)
46[![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=main&svg=true)](https://ci.appveyor.com/project/kowainik/typerep-map)
58[![Hackage](https://img.shields.io/hackage/v/typerep-map.svg?logo=haskell)](https://hackage.haskell.org/package/typerep-map)
69[![Stackage LTS](http://stackage.org/package/typerep-map/badge/lts)](http://stackage.org/lts/package/typerep-map)
710[![Stackage Nightly](http://stackage.org/package/typerep-map/badge/nightly)](http://stackage.org/nightly/package/typerep-map)
8−[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vrom911/typerep-map/blob/master/LICENSE)
11+[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](LICENSE)
12+
913
1014`typerep-map` introduces `TMap` and `TypeRepMap` — data structures like [`Map`](http://hackage.haskell.org/package/containers-0.6.0.1/docs/Data-Map-Lazy.html#t:Map), but where types serve as keys, and values have the types specified in the corresponding key spots.
1115
appveyor.ymlnew file+35
@@ -0,0 +1,35 @@
1+clone_folder: "c:\\WORK"
2+clone_depth: 5
3+
4+# Do not build feature branch with open Pull Requests
5+skip_branch_with_pr: true
6+
7+platform:
8+ - x86
9+ - x86_64
10+
11+cache:
12+ - "C:\\SR"
13+ - dist-newstyle
14+
15+environment:
16+ global:
17+ CABOPTS: --store-dir=C:\\SR
18+
19+ matrix:
20+ - GHCVER: 8.8.3
21+
22+install:
23+ - choco source add -n mistuke -s https://www.myget.org/F/mistuke/api/v2
24+ - choco install -y cabal --version 3.0.0.0
25+ - choco install -y ghc --version 8.8.3
26+ - refreshenv
27+
28+before_build:
29+ - cabal --version
30+ - ghc --version
31+ - cabal %CABOPTS% update
32+
33+build_script:
34+ - cabal %CABOPTS% build --enable-tests --write-ghc-environment-files=always
35+ - cabal %CABOPTS% test --enable-tests
benchmark/CMap.hs+46−39
@@ -1,70 +1,77 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
13{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE ExplicitNamespaces #-}
3−{-# LANGUAGE KindSignatures #-}
44{-# LANGUAGE PolyKinds #-}
55{-# LANGUAGE TypeFamilies #-}
66{-# LANGUAGE TypeOperators #-}
77{-# LANGUAGE UndecidableInstances #-}
88
9−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
10−
119module CMap
12− ( spec
13− ) where
14−
15−import Criterion.Main (bench, nf, env, whnf)
10+ ( spec
11+ ) where
1612
1713import Prelude hiding (lookup)
1814
19−import Spec
15+import Criterion.Main (bench, env, nf, whnf)
16+import Data.Kind (Type)
2017import Data.Maybe (fromJust)
2118import Data.Proxy (Proxy (..))
2219import Data.Typeable (Typeable)
23−import GHC.TypeLits
20+import GHC.TypeLits (type (+), KnownNat, Nat)
2421
2522import Data.TypeRep.CMap (TypeRepMap (..), empty, insert, lookup)
2623
24+import Spec (BenchSpec (..))
25+
26+
2727spec :: BenchSpec
2828spec = BenchSpec
29− { benchLookup = Just $ \name ->
30− env (mkMap 10000) $ \ ~bigMap ->
31− bench name $ nf tenLookups bigMap
32− , benchInsertSmall = Just $ \name ->
33− bench name $ whnf (inserts empty 10) (Proxy @ 99999)
34− , benchInsertBig = Just $ \name ->
35− env (mkMap 10000) $ \ ~(bigMap) ->
36− bench name $ whnf (inserts bigMap 1) (Proxy @ 99999)
37− , benchUpdateSmall = Just $ \name ->
38− env (mkMap 10) $ \ ~(smallMap) ->
39− bench name $ whnf (inserts smallMap 10) (Proxy @ 0)
40− , benchUpdateBig = Just $ \name ->
41− env (mkMap 10000) $ \ ~(bigMap) ->
42− bench name $ whnf (inserts bigMap 10) (Proxy @ 0)
43− }
29+ { benchLookup = Just $ \name ->
30+ env (mkMap 10000) $ \ ~bigMap ->
31+ bench name $ nf tenLookups bigMap
32+ , benchInsertSmall = Just $ \name ->
33+ bench name $ whnf (inserts empty 10) (Proxy @ 99999)
34+ , benchInsertBig = Just $ \name ->
35+ env (mkMap 10000) $ \ ~bigMap ->
36+ bench name $ whnf (inserts bigMap 1) (Proxy @ 99999)
37+ , benchUpdateSmall = Just $ \name ->
38+ env (mkMap 10) $ \ ~smallMap ->
39+ bench name $ whnf (inserts smallMap 10) (Proxy @ 0)
40+ , benchUpdateBig = Just $ \name ->
41+ env (mkMap 10000) $ \ ~bigMap ->
42+ bench name $ whnf (inserts bigMap 10) (Proxy @ 0)
43+ }
4444
45−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
46− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
47− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
48− )
45+tenLookups
46+ :: TypeRepMap (Proxy :: Nat -> Type)
47+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
48+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
49+ )
4950tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
5051 where
5152 lp :: forall (a::Nat). Typeable a => Proxy a
5253 lp = fromJust $ lookup tmap
5354
54−inserts :: forall a . (KnownNat a)
55− => TypeRepMap (Proxy :: Nat -> *)
56− -> Int
57− -> Proxy (a :: Nat)
58− -> TypeRepMap (Proxy :: Nat -> *)
55+inserts
56+ :: forall a . (KnownNat a)
57+ => TypeRepMap (Proxy :: Nat -> Type)
58+ -> Int
59+ -> Proxy (a :: Nat)
60+ -> TypeRepMap (Proxy :: Nat -> Type)
5961inserts !c 0 _ = c
6062inserts !c n x = inserts
61− (insert x c)
62− (n-1)
63− (Proxy :: Proxy (a+1))
63+ (insert x c)
64+ (n-1)
65+ (Proxy :: Proxy (a+1))
6466
65−mkMap :: Int -> IO (TypeRepMap (Proxy :: Nat -> *))
67+mkMap :: Int -> IO (TypeRepMap (Proxy :: Nat -> Type))
6668mkMap n = pure $ buildBigMap n (Proxy :: Proxy 0) empty
6769
68−buildBigMap :: forall a . (KnownNat a) => Int -> Proxy (a :: Nat) -> TypeRepMap (Proxy :: Nat -> *) -> TypeRepMap (Proxy :: Nat -> *)
70+buildBigMap
71+ :: forall a . (KnownNat a)
72+ => Int
73+ -> Proxy (a :: Nat)
74+ -> TypeRepMap (Proxy :: Nat -> Type)
75+ -> TypeRepMap (Proxy :: Nat -> Type)
6976buildBigMap 1 x = insert x
7077buildBigMap n x = insert x . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
benchmark/CacheMap.hs+48−45
@@ -1,76 +1,79 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
13{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE ExplicitNamespaces #-}
3−{-# LANGUAGE KindSignatures #-}
44{-# LANGUAGE PolyKinds #-}
55{-# LANGUAGE TypeFamilies #-}
66{-# LANGUAGE TypeOperators #-}
77{-# LANGUAGE UndecidableInstances #-}
88
9−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
10−
119module CacheMap
12− ( spec
13− ) where
14−
15−import Criterion.Main (bench, nf, whnf, env)
16−import Spec
10+ ( spec
11+ ) where
1712
1813import Prelude hiding (lookup)
1914
15+import Criterion.Main (bench, env, nf, whnf)
16+import Data.Kind (Type)
2017import Data.Maybe (fromJust)
2118import Data.Proxy (Proxy (..))
2219import Data.Typeable (Typeable)
2320import GHC.Exts (fromList)
24−import GHC.TypeLits
21+import GHC.TypeLits (type (+), KnownNat, Nat)
22+
23+import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), empty, insert, lookup)
24+
25+import Spec (BenchSpec (..))
2526
26−import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), lookup, insert, empty)
2727
2828spec :: BenchSpec
2929spec = BenchSpec
30− { benchLookup = Just $ \name ->
31− env (mkMap 10000) $ \ ~bigMap ->
32− bench name $ nf tenLookups bigMap
33− , benchInsertSmall = Just $ \name ->
34− bench name $ whnf (inserts empty 10) (Proxy @ 99999)
35− , benchInsertBig = Just $ \name ->
36− env (mkMap 10000) $ \ ~(bigMap) ->
37− bench name $ whnf (inserts bigMap 1) (Proxy @ 99999)
38− , benchUpdateSmall = Just $ \name ->
39− env (mkMap 10) $ \ ~(smallMap) ->
40− bench name $ whnf (inserts smallMap 10) (Proxy @ 0)
41− , benchUpdateBig = Just $ \name ->
42− env (mkMap 10000) $ \ ~(bigMap) ->
43− bench name $ whnf (inserts bigMap 10) (Proxy @ 0)
44− }
30+ { benchLookup = Just $ \name ->
31+ env (mkMap 10000) $ \ ~bigMap ->
32+ bench name $ nf tenLookups bigMap
33+ , benchInsertSmall = Just $ \name ->
34+ bench name $ whnf (inserts empty 10) (Proxy @ 99999)
35+ , benchInsertBig = Just $ \name ->
36+ env (mkMap 10000) $ \ ~bigMap ->
37+ bench name $ whnf (inserts bigMap 1) (Proxy @ 99999)
38+ , benchUpdateSmall = Just $ \name ->
39+ env (mkMap 10) $ \ ~smallMap ->
40+ bench name $ whnf (inserts smallMap 10) (Proxy @ 0)
41+ , benchUpdateBig = Just $ \name ->
42+ env (mkMap 10000) $ \ ~bigMap ->
43+ bench name $ whnf (inserts bigMap 10) (Proxy @ 0)
44+ }
4545
46−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
47− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
48− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
49− )
46+tenLookups
47+ :: TypeRepMap (Proxy :: Nat -> Type)
48+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
49+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
50+ )
5051tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
5152 where
52− lp :: forall (a::Nat). Typeable a => Proxy a
53+ lp :: forall (a :: Nat) . Typeable a => Proxy a
5354 lp = fromJust $ lookup tmap
5455
55−inserts :: forall a . (KnownNat a)
56− => TypeRepMap (Proxy :: Nat -> *)
57− -> Int
58− -> Proxy (a :: Nat)
59− -> TypeRepMap (Proxy :: Nat -> *)
56+inserts
57+ :: forall a . (KnownNat a)
58+ => TypeRepMap (Proxy :: Nat -> Type)
59+ -> Int
60+ -> Proxy (a :: Nat)
61+ -> TypeRepMap (Proxy :: Nat -> Type)
6062inserts !c 0 _ = c
6163inserts !c n x = inserts
62− (insert x c)
63− (n-1)
64− (Proxy :: Proxy (a+1))
64+ (insert x c)
65+ (n-1)
66+ (Proxy :: Proxy (a + 1))
6567
66−mkMap :: Int -> IO (TypeRepMap (Proxy :: Nat -> *))
68+mkMap :: Int -> IO (TypeRepMap (Proxy :: Nat -> Type))
6769mkMap n = pure $ fromList $ buildBigMap n (Proxy :: Proxy 0) []
6870
6971
70−buildBigMap :: forall a . (KnownNat a)
71− => Int
72− -> Proxy (a :: Nat)
73− -> [WrapTypeable (Proxy :: Nat -> *)]
74− -> [WrapTypeable (Proxy :: Nat -> *)]
72+buildBigMap
73+ :: forall a . (KnownNat a)
74+ => Int
75+ -> Proxy (a :: Nat)
76+ -> [WrapTypeable (Proxy :: Nat -> Type)]
77+ -> [WrapTypeable (Proxy :: Nat -> Type)]
7578buildBigMap 1 x = (WrapTypeable x :)
7679buildBigMap n x = (WrapTypeable x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
benchmark/DMap.hs+45−42
@@ -1,79 +1,81 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
13{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE ExplicitNamespaces #-}
34{-# LANGUAGE InstanceSigs #-}
4−{-# LANGUAGE KindSignatures #-}
55{-# LANGUAGE PolyKinds #-}
66{-# LANGUAGE TypeFamilies #-}
77{-# LANGUAGE TypeOperators #-}
88{-# LANGUAGE UndecidableInstances #-}
99
10−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
11−
1210module DMap
13− ( spec
14− ) where
15−
16−import Criterion.Main (bench, env, nf, whnf)
11+ ( spec
12+ ) where
1713
1814import Prelude hiding (lookup)
1915
2016import Control.DeepSeq (NFData (..))
17+import Criterion.Main (bench, env, nf, whnf)
18+import Data.Kind (Type)
2119import Data.Maybe (fromJust)
2220import Data.Proxy (Proxy (..))
23−import GHC.TypeLits
24−import Spec
21+import GHC.TypeLits (type (+), KnownNat, Nat)
2522import Type.Reflection (TypeRep, Typeable, typeRep)
2623import Type.Reflection.Unsafe (typeRepFingerprint)
2724
2825import Data.Dependent.Map (DMap, empty, insert, keys, lookup)
2926import Data.Some (Some (Some))
3027
31−type TypeRepMap = DMap TypeRep
28+import Spec (BenchSpec (..))
3229
3330
31+type TypeRepMap = DMap TypeRep
32+
3433spec :: BenchSpec
3534spec = BenchSpec
36− { benchLookup = Just $ \name ->
37− env mkBigMap $ \ ~(DMapNF bigMap) ->
38− bench name $ nf tenLookups bigMap
39− , benchInsertSmall = Just $ \name ->
40− bench name $ whnf (inserts empty 10) (Proxy @ 99999)
41− , benchInsertBig = Just $ \name ->
42− env mkBigMap $ \ ~(DMapNF bigMap) ->
43− bench name $ whnf (inserts bigMap 1) (Proxy @ 99999)
44− , benchUpdateSmall = Nothing -- Not implemented
45− , benchUpdateBig = Nothing -- Not implemented
46− }
35+ { benchLookup = Just $ \name ->
36+ env mkBigMap $ \ ~(DMapNF bigMap) ->
37+ bench name $ nf tenLookups bigMap
38+ , benchInsertSmall = Just $ \name ->
39+ bench name $ whnf (inserts empty 10) (Proxy @ 99999)
40+ , benchInsertBig = Just $ \name ->
41+ env mkBigMap $ \ ~(DMapNF bigMap) ->
42+ bench name $ whnf (inserts bigMap 1) (Proxy @ 99999)
43+ , benchUpdateSmall = Nothing -- Not implemented
44+ , benchUpdateBig = Nothing -- Not implemented
45+ }
4746
48−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
49− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
50− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
51− )
47+tenLookups
48+ :: TypeRepMap (Proxy :: Nat -> Type)
49+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
50+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
51+ )
5252tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
5353 where
5454 lp :: forall (a :: Nat) . Typeable a => Proxy a
5555 lp = fromJust $ lookup (typeRep @a) tmap
5656
57−inserts :: forall a . (KnownNat a)
58− => TypeRepMap (Proxy :: Nat -> *)
59− -> Int
60− -> Proxy (a :: Nat)
61− -> TypeRepMap (Proxy :: Nat -> *)
57+inserts
58+ :: forall a . (KnownNat a)
59+ => TypeRepMap (Proxy :: Nat -> Type)
60+ -> Int
61+ -> Proxy (a :: Nat)
62+ -> TypeRepMap (Proxy :: Nat -> Type)
6263inserts !c 0 _ = c
6364inserts !c n x = inserts
64− (insert (typeRep @ a) x c)
65− (n-1)
66− (Proxy :: Proxy (a+1))
65+ (insert (typeRep @ a) x c)
66+ (n-1)
67+ (Proxy :: Proxy (a+1))
6768
6869-- TypeRepMap of 10000 elements
69−mkBigMap :: IO (DMapNF (Proxy :: Nat -> *))
70+mkBigMap :: IO (DMapNF (Proxy :: Nat -> Type))
7071mkBigMap = pure . DMapNF $ buildBigMap 10000 (Proxy :: Proxy 0) empty
7172
72−buildBigMap :: forall a . (KnownNat a)
73− => Int
74− -> Proxy (a :: Nat)
75− -> TypeRepMap (Proxy :: Nat -> *)
76− -> TypeRepMap (Proxy :: Nat -> *)
73+buildBigMap
74+ :: forall a . (KnownNat a)
75+ => Int
76+ -> Proxy (a :: Nat)
77+ -> TypeRepMap (Proxy :: Nat -> Type)
78+ -> TypeRepMap (Proxy :: Nat -> Type)
7779buildBigMap 1 x = insert (typeRep @a) x
7880buildBigMap n x = insert (typeRep @a) x
7981 . buildBigMap (n - 1) (Proxy @(a + 1))
@@ -82,5 +84,6 @@ buildBigMap n x = insert (typeRep @a) x
8284newtype DMapNF f = DMapNF (TypeRepMap f)
8385
8486instance NFData (DMapNF f) where
85− rnf (DMapNF x) =
86− rnf . map (\(Some t) -> typeRepFingerprint t) $ keys x
87+ rnf :: DMapNF f -> ()
88+ rnf (DMapNF x) =
89+ rnf . map (\(Some t) -> typeRepFingerprint t) $ keys x
benchmark/Main.hs+37−32
@@ -1,47 +1,52 @@
11{-# LANGUAGE CPP #-}
22
3−module Main where
3+module Main
4+ ( main
5+ ) where
46
5−import Criterion.Main (defaultMain, bgroup)
7+import Criterion.Main (bgroup, defaultMain)
8+
9+import Spec (BenchSpec (..))
610
7−import Spec
8−import qualified CMap
911import qualified CacheMap
12+import qualified CMap
1013#if ( __GLASGOW_HASKELL__ >= 802 )
1114import qualified DMap
1215#endif
1316import qualified OptimalVector as OptVec
1417
18+
1519main :: IO ()
1620main = do
17− let specs = [("CMap", CMap.spec)
18− ,("CacheMap", CacheMap.spec)
21+ let specs = [("CMap", CMap.spec)
22+ ,("CacheMap", CacheMap.spec)
1923#if ( __GLASGOW_HASKELL__ >= 802 )
20− , ("DMap", DMap.spec)
24+ , ("DMap", DMap.spec)
2125#endif
22− , ("OptVec", OptVec.spec)
23− ]
24− -- This code creates a benchmark group. Given a getter
25− -- (that is test description) it gets a benchmark generation
26− -- function from each module spec. Benchmark generation
27− -- function takes a label and generate benchmarks. It's
28− -- possible to introduce parameters passing in the same way.
29− mkGroup getBenchmark =
30− [ mkBenchmark label
31− | (label, spec) <- specs
32− -- Here we use pure to force pattern matching in List
33− -- then in case of pattern match failure `mzero` will
34− -- be called, so benchmark will be ignored.
35− , Just mkBenchmark <- pure $ getBenchmark spec
26+ , ("OptVec", OptVec.spec)
27+ ]
28+ {- This code creates a benchmark group. Given a getter
29+ (that is test description) it gets a benchmark generation
30+ function from each module spec. Benchmark generation
31+ function takes a label and generate benchmarks. It's
32+ possible to introduce parameters passing in the same way.
33+ --}
34+ let mkGroup getBenchmark =
35+ [ mkBenchmark label
36+ | (label, spec) <- specs
37+ -- Here we use pure to force pattern matching in List
38+ -- then in case of pattern match failure `mzero` will
39+ -- be called, so benchmark will be ignored.
40+ , Just mkBenchmark <- pure $ getBenchmark spec
41+ ]
42+ defaultMain
43+ [ bgroup "lookup" $ mkGroup benchLookup
44+ , bgroup "insert"
45+ [ bgroup "10 elements to empty" $ mkGroup benchInsertSmall
46+ , bgroup "1 element to big map" $ mkGroup benchInsertBig
47+ ]
48+ , bgroup "update"
49+ [ bgroup "10 elements to empty" $ mkGroup benchUpdateSmall
50+ , bgroup "1 element to big map" $ mkGroup benchUpdateBig
51+ ]
3652 ]
37− defaultMain
38− [ bgroup "lookup" $ mkGroup benchLookup
39− , bgroup "insert"
40− [ bgroup "10 elements to empty" $ mkGroup benchInsertSmall
41− , bgroup "1 element to big map" $ mkGroup benchInsertBig
42− ]
43− , bgroup "update"
44− [ bgroup "10 elements to empty" $ mkGroup benchUpdateSmall
45− , bgroup "1 element to big map" $ mkGroup benchUpdateBig
46− ]
47− ]
benchmark/OptimalVector.hs+29−28
@@ -1,59 +1,60 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
13{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE ExplicitNamespaces #-}
3−{-# LANGUAGE KindSignatures #-}
44{-# LANGUAGE PolyKinds #-}
55{-# LANGUAGE ScopedTypeVariables #-}
66{-# LANGUAGE TypeFamilies #-}
77{-# LANGUAGE TypeOperators #-}
88{-# LANGUAGE UndecidableInstances #-}
99
10−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
11−
1210module OptimalVector
1311 ( spec
1412 ) where
1513
16−import Spec
17−import Criterion.Main (bench, nf, env)
18−
1914import Prelude hiding (lookup)
2015
16+import Criterion.Main (bench, env, nf)
17+import Data.Kind (Type)
2118import Data.Maybe (fromJust)
2219import Data.Proxy (Proxy (..))
2320import Data.Typeable (Typeable)
24−import GHC.TypeLits
21+import GHC.TypeLits (type (+), KnownNat, Nat)
2522
2623import Data.TypeRep.OptimalVector (TF (..), TypeRepMap (..), fromList, lookup)
2724
25+import Spec (BenchSpec (..))
26+
27+
2828spec :: BenchSpec
2929spec = BenchSpec
30− { benchLookup = Just $ \name ->
31− env mkBigMap $ \ ~bigMap ->
32− bench name $ nf tenLookups bigMap
33− , benchInsertSmall = Nothing -- Not implemented
34− , benchInsertBig = Nothing -- Not implemented
35− , benchUpdateSmall = Nothing -- Not implemented
36− , benchUpdateBig = Nothing -- Not implemented
37− }
38−
39−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
40− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
41− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
42− )
30+ { benchLookup = Just $ \name ->
31+ env mkBigMap $ \ ~bigMap ->
32+ bench name $ nf tenLookups bigMap
33+ , benchInsertSmall = Nothing -- Not implemented
34+ , benchInsertBig = Nothing -- Not implemented
35+ , benchUpdateSmall = Nothing -- Not implemented
36+ , benchUpdateBig = Nothing -- Not implemented
37+ }
38+
39+tenLookups
40+ :: TypeRepMap (Proxy :: Nat -> Type)
41+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
42+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
43+ )
4344tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
4445 where
4546 lp :: forall (a::Nat). Typeable a => Proxy a
4647 lp = fromJust $ lookup tmap
4748
4849-- TypeRepMap of 10000 elements
49−mkBigMap :: IO (TypeRepMap (Proxy :: Nat -> *))
50+mkBigMap :: IO (TypeRepMap (Proxy :: Nat -> Type))
5051mkBigMap = pure $ fromList $ buildBigMap 10000 (Proxy :: Proxy 0) []
5152
52−buildBigMap :: forall a . (KnownNat a)
53− => Int
54− -> Proxy (a :: Nat)
55− -> [TF (Proxy :: Nat -> *)]
56− -> [TF (Proxy :: Nat -> *)]
53+buildBigMap
54+ :: forall a . (KnownNat a)
55+ => Int
56+ -> Proxy (a :: Nat)
57+ -> [TF (Proxy :: Nat -> Type)]
58+ -> [TF (Proxy :: Nat -> Type)]
5759buildBigMap 1 x = (TF x :)
5860buildBigMap n x = (TF x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
59−
benchmark/Spec.hs+61−50
@@ -1,53 +1,64 @@
1−-- | Specification of the benchmarks.
2−-- This module keeps a list of all bencharks, this way
3−-- we can group benchmark by the interesting function, not
4−-- by the implementation.
1+{- | Specification of the benchmarks.
2+
3+This module keeps a list of all bencharks, this way
4+we can group benchmark by the interesting function, not
5+by the implementation.
6+-}
57module Spec
6− ( BenchSpec(..)
7− ) where
8−
9−import Criterion
10−
11−-- | List of benchmarks that each module should provide.
12−-- If implementation can express the benchmark then it
13−-- can return @Nothing@ in that benchmark.
14−--
15−-- Map should contain elements from @1@ to @size of map@
16−-- inserted in ascending order (later that requirement may
17−-- change).
8+ ( BenchSpec(..)
9+ ) where
10+
11+import Criterion (Benchmark)
12+
13+
14+{- | List of benchmarks that each module should provide.
15+If implementation can express the benchmark then it
16+can return @Nothing@ in that benchmark.
17+
18+Map should contain elements from @1@ to @size of map@
19+inserted in ascending order (later that requirement may
20+change).
21+-}
1822data BenchSpec = BenchSpec
19− { benchLookup :: Maybe (String -> Benchmark)
20− -- ^ Basic lookup we look 10 values inside 10k map.
21− --
22− -- Implementation may look like:
23− -- @
24− -- tenLookups :: TypeRepMap (Proxy :: Nat -> *)
25− -- -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
26− -- , Proxy 50, Proxy 60, Proxy 70, Proxy 80
27− -- )
28− -- tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
29− -- @
30− , benchInsertSmall :: Maybe (String -> Benchmark)
31− -- ^ Insert 10 elements into an empty map.
32− --
33− -- Implementation may look like:
34− -- @
35− -- inserts :: forall a . (KnownNat a)
36− -- => TypeRepMap (Proxy :: Nat -> *)
37− -- -> Int
38− -- -> Proxy (a :: Nat)
39− -- -> TypeRepMap (Proxy :: Nat -> *)
40− -- inserts !c 0 _ = c
41− -- inserts !c n x = inserts (insert x c) (n-1) (Proxy :: Proxy (a+1))
42− -- @
43− , benchInsertBig :: Maybe (String -> Benchmark)
44− -- ^ Insert 10 elements into a big map. Implementation is like
45− -- a small map, but should insert values into 10k elements map.
46− , benchUpdateSmall :: Maybe (String -> Benchmark)
47− -- ^ Insert 10 elements into map of 10 elements, where each key
48− -- was already inserted in the map
49− , benchUpdateBig :: Maybe (String -> Benchmark)
50− -- ^ Insert 10 elements into map of 10k elements, where each key
51− -- was already inserted in the map
52− }
23+ { {- | Basic lookup we look 10 values inside 10k map.
24+
25+ Implementation may look like:
26+
27+ @
28+ tenLookups :: TypeRepMap (Proxy :: Nat -> *)
29+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
30+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
31+ )
32+ tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
33+ @
34+ -}
35+ benchLookup :: Maybe (String -> Benchmark)
36+ {- ^ Insert 10 elements into an empty map.
37+
38+ Implementation may look like:
5339
40+ @
41+ inserts
42+ :: forall a . (KnownNat a)
43+ => TypeRepMap (Proxy :: Nat -> *)
44+ -> Int
45+ -> Proxy (a :: Nat)
46+ -> TypeRepMap (Proxy :: Nat -> *)
47+ inserts !c 0 _ = c
48+ inserts !c n x = inserts (insert x c) (n-1) (Proxy :: Proxy (a+1))
49+ @
50+ -}
51+ , benchInsertSmall :: Maybe (String -> Benchmark)
52+ {- ^ Insert 10 elements into a big map. Implementation is like
53+ a small map, but should insert values into 10k elements map.
54+ -}
55+ , benchInsertBig :: Maybe (String -> Benchmark)
56+ {- ^ Insert 10 elements into map of 10 elements, where each key
57+ was already inserted in the map
58+ -}
59+ , benchUpdateSmall :: Maybe (String -> Benchmark)
60+ {- ^ Insert 10 elements into map of 10k elements, where each key
61+ was already inserted in the map
62+ -}
63+ , benchUpdateBig :: Maybe (String -> Benchmark)
64+ }
benchmark/Vector.hs+23−22
@@ -1,31 +1,30 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
13{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE ExplicitNamespaces #-}
3−{-# LANGUAGE KindSignatures #-}
44{-# LANGUAGE PolyKinds #-}
55{-# LANGUAGE ScopedTypeVariables #-}
66{-# LANGUAGE TypeFamilies #-}
77{-# LANGUAGE TypeOperators #-}
88{-# LANGUAGE UndecidableInstances #-}
99
10−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
11−
1210module Vector
13− ( benchVector
14− , prepareBenchVector
15− ) where
16−
17−import Criterion.Main (Benchmark, bench, bgroup, nf)
11+ ( benchVector
12+ , prepareBenchVector
13+ ) where
1814
1915import Prelude hiding (lookup)
2016
2117import Control.DeepSeq (rnf)
22−import Control.Exception
18+import Control.Exception (evaluate)
19+import Criterion.Main (Benchmark, bench, bgroup, nf)
20+import Data.Kind (Type)
2321import Data.Maybe (fromJust)
2422import Data.Proxy (Proxy (..))
2523import Data.Typeable (Typeable)
26−import GHC.TypeLits
24+import GHC.TypeLits (type (+), KnownNat, Nat)
25+
26+import Data.TypeRep.Vector (TF (..), TypeRepVector, fingerprints, fromList, lookup)
2727
28−import Data.TypeRep.Vector
2928
3029benchVector :: Benchmark
3130benchVector = bgroup "vector"
@@ -34,24 +33,26 @@ benchVector = bgroup "vector"
3433 -- , bench "update old" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 1)
3534 ]
3635
37−tenLookups :: TypeRepVector (Proxy :: Nat -> *)
38− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
39− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
40− )
36+tenLookups
37+ :: TypeRepVector (Proxy :: Nat -> Type)
38+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
39+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
40+ )
4141tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
4242 where
4343 lp :: forall (a::Nat). Typeable a => Proxy a
4444 lp = fromJust $ lookup tmap
4545
4646-- TypeRepMap of 10000 elements
47−bigMap :: TypeRepVector (Proxy :: Nat -> *)
47+bigMap :: TypeRepVector (Proxy :: Nat -> Type)
4848bigMap = fromList $ buildBigMap 10000 (Proxy :: Proxy 0) []
4949
50−buildBigMap :: forall a . (KnownNat a)
51− => Int
52− -> Proxy (a :: Nat)
53− -> [TF (Proxy :: Nat -> *)]
54− -> [TF (Proxy :: Nat -> *)]
50+buildBigMap
51+ :: forall a . (KnownNat a)
52+ => Int
53+ -> Proxy (a :: Nat)
54+ -> [TF (Proxy :: Nat -> Type)]
55+ -> [TF (Proxy :: Nat -> Type)]
5556buildBigMap 1 x = (TF x :)
5657buildBigMap n x = (TF x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
5758
src/Data/TMap.hs+48−2
@@ -37,14 +37,19 @@ module Data.TMap
3737 , delete
3838 , unionWith
3939 , union
40+ , intersectionWith
41+ , intersection
4042 , map
4143 , adjust
44+ , alter
4245
4346 -- * Query
4447 , lookup
4548 , member
4649 , size
4750 , keys
51+ , keysWith
52+ , toListWith
4853 ) where
4954
5055import Prelude hiding (lookup, map)
@@ -52,7 +57,7 @@ import Prelude hiding (lookup, map)
5257import Data.Functor.Identity (Identity (..))
5358import Data.Typeable (Typeable)
5459import GHC.Exts (coerce)
55−import Type.Reflection (SomeTypeRep)
60+import Type.Reflection (SomeTypeRep, TypeRep)
5661
5762import qualified Data.TypeRepMap as F
5863
@@ -87,6 +92,7 @@ one x = coerce (F.one @a @Identity $ coerce x)
8792{- |
8893
8994Insert a value into a 'TMap'.
95+TMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
9096
9197prop> size (insert v tm) >= size tm
9298prop> member @a (insert (x :: a) tm) == True
@@ -98,6 +104,9 @@ insert x = coerce (F.insert @a @Identity $ coerce x)
98104
99105{- | Delete a value from a 'TMap'.
100106
107+TMap optimizes for fast reads rather than modifications, as a trade-off deletes are @O(n)@,
108+with an @O(log(n))@ optimization for when the element is already missing.
109+
101110prop> size (delete @a tm) <= size tm
102111prop> member @a (delete @a tm) == False
103112
@@ -127,6 +136,24 @@ union :: TMap -> TMap -> TMap
127136union = F.union
128137{-# INLINE union #-}
129138
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+
130157{- | Lookup a value of the given type in a 'TMap'.
131158
132159>>> x = lookup $ insert (11 :: Int) empty
@@ -160,6 +187,16 @@ keys :: TMap -> [SomeTypeRep]
160187keys = F.keys
161188{-# INLINE keys #-}
162189
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+
163200-- | Map a function over the values.
164201map :: (forall a. Typeable a => a -> a) -> TMap -> TMap
165202map f = F.hoistWithKey (liftToIdentity f)
@@ -170,5 +207,14 @@ adjust :: Typeable a => (a -> a) -> TMap -> TMap
170207adjust f = F.adjust (liftToIdentity f)
171208{-# INLINE adjust #-}
172209
173−liftToIdentity :: forall a. Typeable a => (a -> a) -> Identity a -> Identity a
210+-- | Updates a value at a specific key, whether or not it exists.
211+-- This can be used to insert, delete, or update a value of a given type in the map.
212+alter :: Typeable a => (Maybe a -> Maybe a) -> TMap -> TMap
213+alter f = F.alter (liftF f)
214+ where
215+ liftF :: forall a. (Maybe a -> Maybe a) -> Maybe (Identity a) -> Maybe (Identity a)
216+ liftF = coerce
217+{-# INLINE alter #-}
218+
219+liftToIdentity :: forall a. (a -> a) -> Identity a -> Identity a
174220liftToIdentity = coerce
src/Data/TypeRepMap.hs+6−1
@@ -5,7 +5,7 @@ Copyright: (c) 2017-2020 Kowainik
55SPDX-License-Identifier: MPL-2.0
66Maintainer: Kowainik <xrom.xkov@gmail.com>
77
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
99sort of parametrization may be familiar to users of @vinyl@ records.
1010
1111@'TypeRepMap' f@ is a more efficient replacement for @DMap
@@ -57,17 +57,22 @@ module Data.TypeRepMap
5757 , insert
5858 , delete
5959 , adjust
60+ , alter
6061 , hoist
6162 , hoistA
6263 , hoistWithKey
6364 , unionWith
6465 , union
66+ , intersectionWith
67+ , intersection
6568
6669 -- * Query
6770 , lookup
6871 , member
6972 , size
7073 , keys
74+ , keysWith
75+ , toListWith
7176
7277 -- * 'IsList'
7378 , WrapTypeable (..)
src/Data/TypeRepMap/Internal.hs+198−50
@@ -1,10 +1,11 @@
1+{-# OPTIONS_GHC -Wno-missing-export-lists #-}
2+
13{-# LANGUAGE AllowAmbiguousTypes #-}
24{-# LANGUAGE BangPatterns #-}
35{-# LANGUAGE CPP #-}
46{-# LANGUAGE DataKinds #-}
57{-# LANGUAGE GADTs #-}
68{-# LANGUAGE InstanceSigs #-}
7−{-# LANGUAGE KindSignatures #-}
89{-# LANGUAGE MagicHash #-}
910{-# LANGUAGE PolyKinds #-}
1011{-# LANGUAGE Rank2Types #-}
@@ -44,9 +45,9 @@ import Data.Kind (Type)
4445import Data.Type.Equality ((:~:) (..), TestEquality (..))
4546import Data.List (intercalate, nubBy)
4647import Data.Maybe (fromMaybe)
47−import Data.Primitive.Array (Array, MutableArray, indexArray, mapArray', readArray, sizeofArray,
48+import Data.Primitive.Array (Array, MutableArray, indexArray, mapArray', sizeofArray,
4849 thawArray, unsafeFreezeArray, writeArray)
49−import Data.Primitive.PrimArray (PrimArray, indexPrimArray, sizeofPrimArray)
50+import Data.Primitive.PrimArray (writePrimArray, newPrimArray, unsafeFreezePrimArray, primArrayToList, MutablePrimArray, primArrayFromListN, PrimArray, indexPrimArray, sizeofPrimArray)
5051import Data.Semigroup (Semigroup (..), All(..))
5152import GHC.Base (Any, Int (..), Int#, (*#), (+#), (<#))
5253import GHC.Exts (IsList (..), inline, sortWith)
@@ -63,7 +64,6 @@ import Type.Reflection (SomeTypeRep (..), TypeRep, Typeable, typeRep, withTypeab
6364import Type.Reflection.Unsafe (typeRepFingerprint)
6465import Unsafe.Coerce (unsafeCoerce)
6566
66−import qualified Data.Map.Strict as Map
6767import qualified GHC.Exts as GHC (fromList, toList)
6868
6969{- |
@@ -169,33 +169,49 @@ prop> member @a (one (x :: f a)) == True
169169
170170-}
171171one :: forall a f . Typeable a => f a -> TypeRepMap f
172−one x = insert x empty
172+one x = TypeRepMap (primArrayFromListN 1 [fa])
173+ (primArrayFromListN 1 [fb])
174+ (pure @Array v)
175+ (pure @Array k)
176+ where
177+ (Fingerprint fa fb, v, k) = (calcFp @a, toAny x, unsafeCoerce $ typeRep @a)
173178{-# INLINE one #-}
174179
175180{- |
176181
177182Insert a value into a 'TypeRepMap'.
183+TypeRepMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
178184
179185prop> size (insert v tm) >= size tm
180186prop> member @a (insert (x :: f a) tm) == True
181187
182188-}
183189insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
184−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)}
185195 where
186− tripleX :: (Fingerprint, Any, Any)
187− tripleX@(fpX, _, _) = (calcFp @a, toAny x, unsafeCoerce $ typeRep @a)
188−
189− addX :: [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)]
190− addX l = tripleX : deleteByFst fpX l
196+ changeAnyArr :: Int -> Array Any -> Array Any
197+ changeAnyArr i trAs = runST $ do
198+ let n = sizeofArray trAs
199+ mutArr <- thawArray trAs 0 n
200+ writeArray mutArr i $ toAny x
201+ unsafeFreezeArray mutArr
191202{-# INLINE insert #-}
192203
193204-- Extract the kind of a type. We use it to work around lack of syntax for
194205-- inferred type variables (which are not subject to type applications).
195206type KindOf (a :: k) = k
196207
208+type ArgKindOf (f :: k -> l) = k
209+
197210{- | Delete a value from a 'TypeRepMap'.
198211
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.
214+
199215prop> size (delete @a tm) <= size tm
200216prop> member @a (delete @a tm) == False
201217
@@ -208,30 +224,60 @@ False
208224True
209225-}
210226delete :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> TypeRepMap f
211−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
212234{-# INLINE delete #-}
213235
236+deleteFirst :: (a -> Bool) -> [a] -> [a]
237+deleteFirst _ [] = []
238+deleteFirst p (x : xs) = if p x then xs else x : deleteFirst p xs
239+
214240{- |
215−Update a value at a specific key with the result of the provided function. When
216−the key is not a member of the map, the original map is returned.
241+Update a value at a specific key with the result of the provided function.
242+When the key is not a member of the map, the original map is returned.
217243
218244>>> trmap = fromList @(TypeRepMap Identity) [WrapTypeable $ Identity "a"]
219245>>> lookup @String $ adjust (fmap (++ "ww")) trmap
220246Just (Identity "aww")
221247-}
222248adjust :: forall a f . Typeable a => (f a -> f a) -> TypeRepMap f -> TypeRepMap f
223−adjust fun tr = case cachedBinarySearch (typeFp @a) (fingerprintAs tr) (fingerprintBs tr) of
224− Nothing -> tr
225− Just i -> tr {trAnys = changeAnyArr i (trAnys tr)}
249+adjust fun = alter (fmap fun)
250+{-# INLINE adjust #-}
251+
252+{- |
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.
255+
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")
262+-}
263+alter :: forall a f . Typeable a => (Maybe (f a) -> Maybe (f a)) -> TypeRepMap f -> TypeRepMap f
264+alter fun tr = case cachedBinarySearch (typeFp @a) (fingerprintAs tr) (fingerprintBs tr) of
265+ Nothing ->
266+ case (fun Nothing) of
267+ Nothing -> tr
268+ Just v -> insert v tr
269+ Just i ->
270+ case fun (Just . fromAny $ indexArray (trAnys tr) i) of
271+ Nothing -> delete @a tr
272+ Just v -> tr{trAnys = replaceAnyAt i (toAny v) (trAnys tr)}
226273 where
227− changeAnyArr :: Int -> Array Any -> Array Any
228− changeAnyArr i trAs = runST $ do
274+ replaceAnyAt :: Int -> Any -> Array Any -> Array Any
275+ replaceAnyAt i v trAs = runST $ do
229276 let n = sizeofArray trAs
230277 mutArr <- thawArray trAs 0 n
231− a <- toAny . fun . fromAny <$> readArray mutArr i
232− writeArray mutArr i a
278+ writeArray mutArr i v
233279 unsafeFreezeArray mutArr
234−{-# INLINE adjust #-}
280+{-# INLINE alter #-}
235281
236282{- | Map over the elements of a 'TypeRepMap'.
237283
@@ -265,33 +311,89 @@ hoistWithKey f (TypeRepMap as bs ans ks) = TypeRepMap as bs newAns ks
265311 withTr t = withTypeable t f
266312{-# INLINE hoistWithKey #-}
267313
268−-- | The union of two 'TypeRepMap's using a combining function.
314+-- | The union of two 'TypeRepMap's using a combining function for conflicting entries. @O(n + m)@
269315unionWith :: forall f. (forall x. Typeable x => f x -> f x -> f x) -> TypeRepMap f -> TypeRepMap f -> TypeRepMap f
270−unionWith f m1 m2 = fromTriples
271− $ toTripleList
272− $ Map.unionWith combine
273− (fromTripleList $ toTriples m1)
274− (fromTripleList $ toTriples m2)
316+unionWith f ma mb = do
317+ fromSortedTriples $ mergeMaps (toSortedTriples ma) (toSortedTriples mb)
275318 where
276319 f' :: forall x. TypeRep x -> f x -> f x -> f x
277320 f' tr = withTypeable tr f
278321
279− combine :: (Any, Any) -> (Any, Any) -> (Any, Any)
280− combine (av, ak) (bv, _) = (toAny $ f' (fromAny ak) (fromAny av) (fromAny bv), ak)
281−
282− fromTripleList :: Ord a => [(a, b, c)] -> Map.Map a (b, c)
283− fromTripleList = Map.fromList . map (\(a, b, c) -> (a, (b, c)))
284−
285− toTripleList :: Map.Map a (b, c) -> [(a, b, c)]
286− toTripleList = map (\(a, (b, c)) -> (a, b, c)) . Map.toList
322+ combine :: (Fingerprint, Any, Any) -> (Fingerprint, Any, Any) -> (Fingerprint, Any, Any)
323+ combine (fp, av, ak) (_, bv, _) = (fp, toAny $ f' (fromAny ak) (fromAny av) (fromAny bv), ak)
324+
325+ -- Merges two typrepmaps into a sorted, dedup'd list of triples.
326+ -- Using 'toSortedTriples' allows us to assume the triples are sorted by fingerprint,
327+ -- Given O(n) performance from 'toSortedTriples', and given that we can merge-sort in
328+ -- O(n + m) time, then can '.fromSortedTriples' back into cachedBinarySearch order in O(n + m)
329+ -- that gives a total of O(n + m).
330+ mergeMaps :: [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)]
331+ -- We've addressed all elements from both maps
332+ mergeMaps as [] = as
333+ mergeMaps [] bs = bs
334+ -- Merge
335+ mergeMaps (a@(af, _, _) : as) (b@(bf, _, _) : bs) =
336+ case compare af bf of
337+ -- Fingerprints are equal, union the elements using our function
338+ -- If the incoming maps were de-duped, there shouldn't be any other equivalent
339+ -- fingerprints
340+ EQ -> combine a b : mergeMaps as bs
341+ -- First fingerprint must not be in the second map or we would have seen it by now
342+ -- Add it to the result as-is
343+ LT -> a : mergeMaps as (b : bs)
344+ -- Second fingerprint must not be in the first map or we would have seen it by now
345+ -- Add it to the result as-is
346+ GT -> b : mergeMaps (a:as) bs
287347{-# INLINE unionWith #-}
288348
289−-- | The (left-biased) union of two 'TypeRepMap's. It prefers the first map when
349+-- | The (left-biased) union of two 'TypeRepMap's in @O(n + m)@. It prefers the first map when
290350-- duplicate keys are encountered, i.e. @'union' == 'unionWith' const@.
291351union :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
292352union = unionWith const
293353{-# INLINE union #-}
294354
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+
295397{- | Check if a value of the given type is present in a 'TypeRepMap'.
296398
297399>>> member @Char $ one (Identity 'a')
@@ -327,9 +429,22 @@ size = sizeofPrimArray . fingerprintAs
327429
328430-- | Return the list of 'SomeTypeRep' from the keys.
329431keys :: TypeRepMap f -> [SomeTypeRep]
330−keys TypeRepMap{..} = SomeTypeRep . anyToTypeRep <$> toList trKeys
432+keys = keysWith SomeTypeRep
331433{-# INLINE keys #-}
332434
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+
333448-- | Binary searched based on this article
334449-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
335450-- with modification for our two-vector search case.
@@ -373,8 +488,20 @@ typeFp = typeRepFingerprint $ typeRep @a
373488toTriples :: TypeRepMap f -> [(Fingerprint, Any, Any)]
374489toTriples tm = zip3 (toFingerprints tm) (GHC.toList $ trAnys tm) (GHC.toList $ trKeys tm)
375490
376−deleteByFst :: Eq a => a -> [(a, b, c)] -> [(a, b, c)]
377−deleteByFst x = filter ((/= x) . fst3)
491+-- | Efficiently get sorted triples from a map in O(n) time
492+--
493+-- We assume the incoming TypeRepMap is already sorted into 'cachedBinarySearch' order using fromSortedList.
494+-- Then we can construct the index mapping from the "cached" ordering into monotonically
495+-- increasing order using 'generateOrderMapping' with the length of the TRM. This takes @O(n).
496+-- We then pull those indexes from the source TRM to get the sorted triples in a total of @O(n).
497+toSortedTriples :: TypeRepMap f -> [(Fingerprint, Any, Any)]
498+toSortedTriples tm = trip <$> ordering
499+ where
500+ trip i = ( Fingerprint (indexPrimArray (fingerprintAs tm) i) (indexPrimArray (fingerprintBs tm) i)
501+ , indexArray (trAnys tm) i
502+ , indexArray (trKeys tm) i)
503+ ordering :: [ Int ]
504+ ordering = generateOrderMapping (size tm)
378505
379506nubByFst :: (Eq a) => [(a, b, c)] -> [(a, b, c)]
380507nubByFst = nubBy ((==) `on` fst3)
@@ -394,9 +521,6 @@ data WrapTypeable f where
394521instance Show (WrapTypeable f) where
395522 show (WrapTypeable (_ :: f a)) = show $ calcFp @a
396523
397−wrapTypeable :: TypeRep a -> f a -> WrapTypeable f
398−wrapTypeable tr = withTypeable tr WrapTypeable
399−
400524{- |
401525
402526prop> fromList . toList == 'id'
@@ -424,19 +548,19 @@ instance IsList (TypeRepMap f) where
424548 k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
425549
426550 toList :: TypeRepMap f -> [WrapTypeable f]
427− toList = map toWrapTypeable . toTriples
428− where
429− toWrapTypeable :: (Fingerprint, Any, Any) -> WrapTypeable f
430− toWrapTypeable (_, an, k) = wrapTypeable (unsafeCoerce k) (fromAny an)
551+ toList = toListWith WrapTypeable
431552
432553calcFp :: forall a . Typeable a => Fingerprint
433554calcFp = typeRepFingerprint $ typeRep @a
434555
435556fromTriples :: [(Fingerprint, Any, Any)] -> TypeRepMap f
436−fromTriples kvs = TypeRepMap (GHC.fromList fpAs) (GHC.fromList fpBs) (GHC.fromList ans) (GHC.fromList ks)
557+fromTriples = fromSortedTriples . sortWith fst3 . nubByFst
558+
559+fromSortedTriples :: [(Fingerprint, Any, Any)] -> TypeRepMap f
560+fromSortedTriples kvs = TypeRepMap (GHC.fromList fpAs) (GHC.fromList fpBs) (GHC.fromList ans) (GHC.fromList ks)
437561 where
438562 (fpAs, fpBs) = unzip $ map (\(Fingerprint a b) -> (a, b)) fps
439− (fps, ans, ks) = unzip3 $ fromSortedList $ sortWith fst3 $ nubByFst kvs
563+ (fps, ans, ks) = unzip3 $ fromSortedList kvs
440564
441565----------------------------------------------------------------------------
442566-- Tree-like conversion
@@ -463,11 +587,35 @@ fromSortedList l = runST $ do
463587 writeArray result i (indexArray origin newFirst)
464588 loop (2 * i + 2) (newFirst + 1)
465589
590+-- Returns a list of indexes which represents the "sorted" order of an array generated by
591+-- fromSortedList of the provided length.
592+-- I.e. fmap (fromSortedList [1, 2, 3, 4, 5, 6] !!) (generateOrderMapping 6) == [1, 2, 3, 4, 5, 6]
593+--
594+-- >>> generateOrderMapping 6
595+-- [3,1,4,0,5,2]
596+--
597+-- >>> generateOrderMapping 8
598+-- [7,3,1,4,0,5,2,6]
599+generateOrderMapping :: Int -> [Int]
600+generateOrderMapping len = runST $ do
601+ orderMappingArr <- newPrimArray len
602+ _ <- loop orderMappingArr 0 0
603+ primArrayToList <$> unsafeFreezePrimArray orderMappingArr
604+ where
605+ loop :: MutablePrimArray s Int -> Int -> Int -> ST s Int
606+ loop result i first =
607+ if i >= len
608+ then pure first
609+ else do
610+ newFirst <- loop result (2 * i + 1) first
611+ writePrimArray result newFirst i
612+ loop result (2 * i + 2) (newFirst + 1)
613+
466614----------------------------------------------------------------------------
467615-- Helper functions.
468616----------------------------------------------------------------------------
469617
470−-- | Check that invariant of the structure is hold.
618+-- | Check that invariant of the structure holds.
471619-- The structure maintains the following invariant.
472620-- For each element @A@ at index @i@:
473621--
test/Test/TypeRep/TypeRepMap.hs+62−4
@@ -5,14 +5,18 @@ module Test.TypeRep.TypeRepMap
55import Prelude hiding (lookup)
66
77import Data.Functor.Identity (Identity (..))
8−import GHC.Exts (fromList)
9−import Test.Hspec (Spec, describe, it, shouldBe)
8+import Data.List (sortBy)
9+import Data.Ord (comparing)
10+import Data.Typeable (cast)
11+import GHC.Exts (fromList, toList)
12+import Type.Reflection (SomeTypeRep(..), typeRep)
13+import Test.Hspec (Spec, describe, it, shouldBe, shouldMatchList, shouldSatisfy, expectationFailure)
1014
11−import Data.TMap (TMap, empty, insert, lookup, one, size, union)
15+import Data.TMap (TMap, empty, insert, lookup, one, size, union, keys)
1216import Data.TypeRepMap.Internal (WrapTypeable (..))
1317
1418
15−-- Simple test for 'lookup', 'insert' and 'size' functions.
19+-- Simple test for 'lookup', 'insert', 'size', 'keys', 'toList' functions.
1620typeRepMapSpec :: Spec
1721typeRepMapSpec = describe "TypeRepMap" $ do
1822 describe "Lookup Test" $ do
@@ -39,6 +43,60 @@ typeRepMapSpec = describe "TypeRepMap" $ do
3943 lookup m `shouldBe` Just True
4044 lookup @Int m `shouldBe` Nothing
4145
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
42100
43101mapOf10 :: TMap
44102mapOf10 = insert True
test/Test/TypeRep/TypeRepMapProperty.hs+70−29
@@ -1,7 +1,6 @@
11{-# LANGUAGE CPP #-}
22{-# LANGUAGE DataKinds #-}
33{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4−{-# LANGUAGE KindSignatures #-}
54{-# LANGUAGE PolyKinds #-}
65
76{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
@@ -15,14 +14,14 @@ import Prelude hiding (lookup)
1514import Data.Proxy (Proxy (..))
1615import Data.Semigroup (Semigroup (..))
1716import GHC.Exts (fromList)
18−import GHC.Stack (HasCallStack)
1917import GHC.TypeLits (Nat, SomeNat (..), someNatVal)
20−import Hedgehog (MonadGen, PropertyT, assert, forAll, property, (===))
18+import Hedgehog (MonadGen, assert, forAll, (===))
2119import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it)
2220import Test.Hspec.Hedgehog (hedgehog)
2321
2422import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), delete, insert, invariantCheck,
25− lookup, member)
23+ lookup, member, union, generateOrderMapping, fromSortedList,
24+ adjust, alter, intersection)
2625
2726import qualified Hedgehog.Gen as Gen
2827import qualified Hedgehog.Range as Range
@@ -36,6 +35,13 @@ typeRepMapPropertySpec = describe "TypeRepMap Property tests" $ do
3635 deleteMemberSpec
3736 insertInvariantSpec
3837 deleteInvariantSpec
38+ alterInsertSpec
39+ alterDeleteSpec
40+ alterAdjustSpec
41+ alterModifySpec
42+ intersectionSpec
43+ describe "Internal helpers" $ do
44+ generateOrderMappingInvariantSpec
3945 describe "Instance Laws" $ do
4046 semigroupAssocSpec
4147 monoidIdentitySpec
@@ -82,45 +88,66 @@ deleteInvariantSpec = it "invariantCheck (delete k b) == True" $ hedgehog $ do
8288 WrapTypeable (_ :: IntProxy n) <- forAll genTF
8389 assert $ invariantCheck (delete @n m)
8490
91+alterInsertSpec :: Property
92+alterInsertSpec = it "insert proxy m === alter (const (Just proxy)) m" $ hedgehog $ do
93+ m <- forAll genMap
94+ WrapTypeable (proxy :: IntProxy n) <- forAll genTF
95+ insert proxy m === alter (const (Just proxy)) m
96+
97+alterDeleteSpec :: Property
98+alterDeleteSpec = it "delete proxy m === alter (const Nothing) m" $ hedgehog $ do
99+ WrapTypeable (proxy :: IntProxy n) <- forAll genTF
100+ m <- insert proxy <$> forAll genMap
101+ delete @n @IntProxy m === alter @n @IntProxy (const Nothing) m
102+
103+alterAdjustSpec :: Property
104+alterAdjustSpec = it "adjust f m == alter (fmap f) m" $ hedgehog $ do
105+ m <- forAll genMap
106+ WrapTypeable (_ :: IntProxy n) <- forAll genTF
107+ let f (IntProxy p n) = IntProxy p (n * 10)
108+ adjust @n @IntProxy f m === alter @n @IntProxy (fmap f) m
109+
110+alterModifySpec :: Property
111+alterModifySpec = it "lookup k (alter f) == f (lookup k m)" $ hedgehog $ do
112+ m <- forAll genMap
113+ WrapTypeable (_ :: IntProxy n) <- forAll genTF
114+ randInt <- forAll (Gen.int Range.constantBounded)
115+ -- Function with some interesting behaviour, which inserts, seletes and modifies
116+ let f Nothing = Just (IntProxy Proxy randInt)
117+ f (Just (IntProxy p n))
118+ | even n = Nothing
119+ | otherwise = Just $ IntProxy p (n * 10)
120+ lookup @n @IntProxy (alter @n f m) === f (lookup @n @IntProxy m)
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+
85128----------------------------------------------------------------------------
86−-- Semigroup and Monoid laws
129+-- Internal helpers
87130----------------------------------------------------------------------------
131+generateOrderMappingInvariantSpec :: Property
132+generateOrderMappingInvariantSpec =
133+ it "fmap (fromSortedList [1 .. n] !!) (generateOrderMapping n) == [1 .. n]" $ hedgehog $ do
134+ n <- forAll $ Gen.int (Range.linear 0 100)
135+ fmap (fromSortedList [1 .. n] !!) (generateOrderMapping n) === [1 .. n]
88136
89−#if __GLASGOW_HASKELL__ < 806
90−{- | This newtype is used to compare 'TypeRepMap's using only 'Fingerprint's.
91−It's not a good idea to write such 'Eq' instance for 'TypeRepMap' itself because
92−it doesn't compare values so it's not true equality. But this should be enough
93−for tests.
94−-}
95−newtype FpMap f = FpMap (TypeRepMap f)
96− deriving newtype (Show, Semigroup, Monoid)
97−
98−instance Eq (FpMap f) where
99− FpMap (TypeRepMap as1 bs1 _ _) == FpMap (TypeRepMap as2 bs2 _ _) =
100− as1 == as2 && bs1 == bs2
101−#endif
137+----------------------------------------------------------------------------
138+-- Semigroup and Monoid laws
139+----------------------------------------------------------------------------
102140
103141semigroupAssocSpec :: Property
104142semigroupAssocSpec = it "x <> (y <> z) == (x <> y) <> z" $ hedgehog $ do
105−#if __GLASGOW_HASKELL__ >= 806
106143 x <- forAll genMap
107144 y <- forAll genMap
108145 z <- forAll genMap
109−#else
110− x <- FpMap <$> forAll genMap
111− y <- FpMap <$> forAll genMap
112− z <- FpMap <$> forAll genMap
113−#endif
114146 (x <> (y <> z)) === ((x <> y) <> z)
115147
116148monoidIdentitySpec :: Property
117149monoidIdentitySpec = it "x <> mempty == mempty <> x == x" $ hedgehog $ do
118−#if __GLASGOW_HASKELL__ >= 806
119150 x <- forAll genMap
120−#else
121− x <- FpMap <$> forAll genMap
122−#endif
123−
124151 x <> mempty === x
125152 mempty <> x === x
126153
@@ -141,3 +168,17 @@ genTF = do
141168 case someNatVal randNat of
142169 Just (SomeNat proxyNat) -> pure $ WrapTypeable $ IntProxy proxyNat randInt
143170 Nothing -> error "Invalid test generator"
171+
172+----------------------------------------------------------------------------
173+-- Helpers
174+----------------------------------------------------------------------------
175+#if __GLASGOW_HASKELL__ < 806
176+{- | We add an orphan Eq instance for old GHC versions just to make testing easier.
177+It's not a good idea to write such 'Eq' instance for 'TypeRepMap' itself because
178+it doesn't compare values so it's not true equality. But this should be enough
179+for tests.
180+-}
181+instance Eq (TypeRepMap f) where
182+ TypeRepMap as1 bs1 _ _ == TypeRepMap as2 bs2 _ _ =
183+ as1 == as2 && bs1 == bs2
184+#endif
typerep-extra-impls/Data/TypeRep/CMap.hs+3−3
@@ -1,5 +1,4 @@
1−{-# LANGUAGE KindSignatures #-}
2−{-# LANGUAGE PolyKinds #-}
1+{-# LANGUAGE PolyKinds #-}
32
43{- |
54Copyright: (c) 2017-2020 Kowainik
@@ -21,6 +20,7 @@ module Data.TypeRep.CMap
2120import Prelude hiding (lookup)
2221
2322import Control.DeepSeq
23+import Data.Kind (Type)
2424import Data.Proxy (Proxy (..))
2525import Data.Typeable (TypeRep, Typeable, typeRep)
2626import GHC.Base (Any)
@@ -30,7 +30,7 @@ import qualified Data.Map.Lazy as LMap
3030
3131
3232-- | Map-like data structure with types served as the keys.
33−newtype TypeRepMap (f :: k -> *) = TypeRepMap
33+newtype TypeRepMap (f :: k -> Type) = TypeRepMap
3434 { unMap :: LMap.Map TypeRep Any
3535 }
3636
typerep-extra-impls/Data/TypeRep/OptimalVector.hs+7−8
@@ -1,9 +1,8 @@
1−{-# LANGUAGE BangPatterns #-}
2−{-# LANGUAGE GADTs #-}
3−{-# LANGUAGE KindSignatures #-}
4−{-# LANGUAGE MagicHash #-}
5−{-# LANGUAGE PolyKinds #-}
6−{-# LANGUAGE TypeFamilies #-}
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE GADTs #-}
3+{-# LANGUAGE MagicHash #-}
4+{-# LANGUAGE PolyKinds #-}
5+{-# LANGUAGE TypeFamilies #-}
76
87{- |
98Copyright: (c) 2017-2020 Kowainik
@@ -62,7 +61,7 @@ empty :: TypeRepMap f
6261empty = TypeRepMap mempty mempty mempty
6362
6463-- | Inserts the value with its type as a key.
65−insert :: forall a f . Typeable a => a -> TypeRepMap f -> TypeRepMap f
64+insert :: forall a f . a -> TypeRepMap f -> TypeRepMap f
6665insert = undefined
6766
6867-- | Looks up the value at the type.
@@ -112,7 +111,7 @@ binarySearch (Fingerprint a b) fpAs fpBs =
112111data TF f where
113112 TF :: Typeable a => f a -> TF f
114113
115−fromF :: Typeable a => f a -> Proxy a
114+fromF :: f a -> Proxy a
116115fromF _ = Proxy
117116
118117fromList :: forall f . [TF f] -> TypeRepMap f
typerep-extra-impls/Data/TypeRep/Vector.hs+2−2
@@ -144,7 +144,7 @@ empty :: TypeRepVector f
144144empty = TypeRepVect mempty mempty
145145
146146-- | Inserts the value with its type as a key.
147−insert :: forall a f . Typeable a => a -> TypeRepVector f -> TypeRepVector f
147+insert :: forall a f . a -> TypeRepVector f -> TypeRepVector f
148148insert = undefined
149149
150150-- | Looks up the value at the type.
@@ -164,7 +164,7 @@ size = Unboxed.length . fingerprints
164164data TF f where
165165 TF :: Typeable a => f a -> TF f
166166
167−fromF :: Typeable a => f a -> Proxy a
167+fromF :: f a -> Proxy a
168168fromF _ = Proxy
169169
170170fromList :: forall f . [TF f] -> TypeRepVector f
typerep-map.cabal+17−3
@@ -48,8 +48,23 @@ common common-options
4848 RecordWildCards
4949 ScopedTypeVariables
5050 TypeApplications
51− if impl(ghc >= 8.8.1)
51+ ghc-options: -Wall
52+ -Wcompat
53+ -Widentities
54+ -Wincomplete-uni-patterns
55+ -Wincomplete-record-updates
56+ if impl(ghc >= 8.0)
57+ ghc-options: -Wredundant-constraints
58+ if impl(ghc >= 8.2)
59+ ghc-options: -fhide-source-paths
60+ if impl(ghc >= 8.4)
61+ ghc-options: -Wmissing-export-lists
62+ -Wpartial-fields
63+ if impl(ghc >= 8.8)
5264 ghc-options: -Wmissing-deriving-strategies
65+ -Werror=missing-deriving-strategies
66+ if impl(ghc >= 8.10)
67+ ghc-options: -Wunused-packages
5368
5469library
5570 import: common-options
@@ -58,8 +73,7 @@ library
5873 Data.TypeRepMap
5974 Data.TypeRepMap.Internal
6075
61− build-depends: containers >= 0.5.10.2 && < 0.7
62− , ghc-prim >= 0.5.1.1 && < 0.7
76+ build-depends: ghc-prim >= 0.5.1.1 && < 0.7
6377 , primitive ^>= 0.7.0
6478 , deepseq ^>= 1.4
6579