on main help sign in
33 files changed+1947−687
since splitsnapshots
unifiedsplit
.github/CODEOWNERS+1 .github/workflows/ci.yml+47 .gitignore+1 .stylish-haskell.yaml+59 .travis.yml+8−66 CHANGELOG.md+49−5 LICENSE+373−21 README.md+18−7 appveyor.yml+35 benchmark/CMap.hs+53−33 benchmark/CacheMap.hs+59−32 benchmark/DMap.hs+56−53 benchmark/Main.hs+41−20 benchmark/OptimalVector.hs+34−36 benchmark/Spec.hs+64 benchmark/Vector.hs+23−22 shell.nix−10 src/Data/TMap.hs+75−3 src/Data/TypeRepMap.hs+56−40 src/Data/TypeRepMap/Internal.hs+433−91 stack.yaml+4−2 test/Test.hs+21−1 test/Test/TypeRep/CMap.hs+9−7 test/Test/TypeRep/CacheMap.hs−48 test/Test/TypeRep/MapProperty.hs−109 test/Test/TypeRep/TypeRepMap.hs+111 test/Test/TypeRep/TypeRepMapProperty.hs+184 test/Test/TypeRep/Vector.hs+8−6 test/Test/TypeRep/VectorOpt.hs+8−6 typerep-extra-impls/Data/TypeRep/CMap.hs+16−3 typerep-extra-impls/Data/TypeRep/OptimalVector.hs+20−8 typerep-extra-impls/Data/TypeRep/Vector.hs+11−2 typerep-map.cabal+70−56
.github/CODEOWNERSnew file+1
@@ -0,0 +1 @@
1+* @chshersh @vrom911
.github/workflows/ci.ymlnew file+47
@@ -0,0 +1,47 @@
1+name: CI
2+
3+# Trigger the workflow on push or pull request, but only for the main branch
4+on:
5+ push:
6+ branches:
7+ - main
8+ pull_request:
9+
10+jobs:
11+ build:
12+ name: ghc ${{ matrix.ghc }}
13+ runs-on: ubuntu-16.04
14+ strategy:
15+ matrix:
16+ cabal: ["3.0"]
17+ ghc:
18+ - "8.2.2"
19+ - "8.4.4"
20+ - "8.6.5"
21+ - "8.8.3"
22+ - "8.10.1"
23+
24+ steps:
25+ - uses: actions/checkout@v2
26+ if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.ref == 'refs/heads/main'
27+
28+ - uses: actions/setup-haskell@v1
29+ name: Setup Haskell
30+ with:
31+ ghc-version: ${{ matrix.ghc }}
32+ cabal-version: ${{ matrix.cabal }}
33+
34+ - uses: actions/cache@v1
35+ name: Cache ~/.cabal/store
36+ with:
37+ path: ~/.cabal/store
38+ key: ${{ runner.os }}-${{ matrix.ghc }}-cabal
39+
40+ - name: Build
41+ run: |
42+ cabal update
43+ cabal build --enable-tests --enable-benchmarks all
44+
45+ - name: Test
46+ run: |
47+ cabal test --enable-tests --test-show-details=direct all
.gitignore+1
@@ -24,6 +24,7 @@ cabal.project.local
2424
2525# Stack
2626.stack-work/
27+stack.yaml.lock
2728
2829### IDE/support
2930# Vim
.stylish-haskell.yamlnew file+59
@@ -0,0 +1,59 @@
1+steps:
2+ - simple_align:
3+ cases: true
4+ top_level_patterns: true
5+ records: true
6+
7+ # Import cleanup
8+ - imports:
9+ align: none
10+ list_align: after_alias
11+ pad_module_names: false
12+ long_list_align: inline
13+ empty_list_align: inherit
14+ list_padding: 4
15+ separate_lists: true
16+ space_surround: false
17+
18+ - language_pragmas:
19+ style: vertical
20+ remove_redundant: true
21+
22+ # Remove trailing whitespace
23+ - trailing_whitespace: {}
24+
25+columns: 100
26+
27+newline: native
28+
29+language_extensions:
30+ - BangPatterns
31+ - ConstraintKinds
32+ - DataKinds
33+ - DefaultSignatures
34+ - DeriveAnyClass
35+ - DeriveDataTypeable
36+ - DeriveGeneric
37+ - DerivingStrategies
38+ - FlexibleContexts
39+ - FlexibleInstances
40+ - FunctionalDependencies
41+ - GADTs
42+ - GeneralizedNewtypeDeriving
43+ - InstanceSigs
44+ - KindSignatures
45+ - LambdaCase
46+ - MultiParamTypeClasses
47+ - MultiWayIf
48+ - NamedFieldPuns
49+ - NoImplicitPrelude
50+ - OverloadedStrings
51+ - QuasiQuotes
52+ - RecordWildCards
53+ - ScopedTypeVariables
54+ - StandaloneDeriving
55+ - TemplateHaskell
56+ - TupleSections
57+ - TypeApplications
58+ - TypeFamilies
59+ - ViewPatterns
.travis.yml+8−66
@@ -6,81 +6,23 @@ git:
66
77cache:
88 directories:
9− - "$HOME/.cabal"
10− - "$HOME/.ghc"
119 - "$HOME/.stack"
12− - "$HOME/build/kowainik/typerep-map/.stack-work"
1310
1411matrix:
15−
1612 include:
17−
18− - ghc: 8.0.2
19− env: GHCVER='8.0.2' CABALVER='head' ALLOWNEWER='--allow-newer'
20− os: linux
21− addons:
22− apt:
23− sources:
24− - hvr-ghc
25− packages:
26− - ghc-8.0.2
27− - cabal-install-head
28−
29− - ghc: 8.2.2
30− env: GHCVER='8.2.2' CABALVER='head'
31− os: linux
32− addons:
33− apt:
34− sources:
35− - hvr-ghc
36− packages:
37− - ghc-8.2.2
38− - cabal-install-head
39−
40− - ghc: 8.4.3
41− env: GHCVER='8.4.3' CABALVER='head'
42− os: linux
43− addons:
44− apt:
45− sources:
46− - hvr-ghc
47− packages:
48− - ghc-8.4.3
49− - cabal-install-head
50−
51− - ghc: 8.4.3
52− env: STACK_YAML="$HOME/build/kowainik/typerep-map/stack.yaml"
13+ - env: GHC='8.8.3'
5314 os: linux
54− addons:
55− apt:
56− packages:
57− - libgmp-dev
15+ - env: GHC='8.8.3'
16+ os: osx
5817
5918install:
60− - |
61− if [ -z "$STACK_YAML" ]; then
62− export PATH="/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH"
63− echo $PATH
64− cabal new-update
65− cabal new-build --enable-tests --enable-benchmarks
66− else
67− echo "$TRAVIS_BUILD_DIR"
68− mkdir -p ~/.local/bin
69− export PATH="$HOME/.local/bin:$PATH"
70− travis_retry curl -L 'https://www.stackage.org/stack/linux-x86_64' | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
71− stack --version
72− stack setup --no-terminal --install-cabal 2.0.1.0
73− stack ghc -- --version
74− stack build --only-dependencies --no-terminal
75− 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
7623
7724script:
78− - |
79− if [ -z "$STACK_YAML" ]; then
80− cabal new-test $ALLOWNEWER
81− else
82− stack build --test --bench --no-terminal
83− fi
25+ - stack test --no-terminal
8426
8527notifications:
8628 email: false
CHANGELOG.md+49−5
@@ -1,12 +1,56 @@
1−Change log
2−==========
1+# Changelog
32
43`typerep-map` uses [PVP Versioning][1].
5−The change log is available [on GitHub][2].
4+The changelog is available [on GitHub][2].
65
7−# 0.1.0
6+## 0.3.3.0 — Apr 18, 2020
7+
8+* [#83](https://github.com/kowainik/typerep-map/issues/83):
9+ Support GHC-8.10.
10+* [#80](https://github.com/kowainik/typerep-map/issues/80):
11+ Build on 32-bit platforms.
12+* [#78](https://github.com/kowainik/typerep-map/issues/78):
13+ Support GHC-8.8.3.
14+* [#63](https://github.com/kowainik/typerep-map/pull/63):
15+ Add invariant check.
16+
17+## 0.3.2 — Mar 27, 2019
18+
19+* [#47](https://github.com/kowainik/typerep-map/issues/47):
20+ Add `Eq` instance for `TypeRepMap` using `-XQuantifiedConstraints`.
21+* [#70](https://github.com/kowainik/typerep-map/issues/70):
22+ Bump up to `dependent-sum-0.5`.
23+
24+## 0.3.1
25+
26+* [#64](https://github.com/kowainik/typerep-map/issues/64):
27+ Fix segfault in `toList`.
28+* Support GHC 8.4.4 and 8.6.3.
29+
30+## 0.3.0
31+
32+* [#46](https://github.com/kowainik/typerep-map/issues/46):
33+ Make `Show` instance for `TypeRepMap` show keys.
34+ Add `keys` function.
35+* [#48](https://github.com/kowainik/typerep-map/issues/48):
36+ Add `adjust` function for `TypeRepMap` and `TMap`.
37+* [#30](https://github.com/kowainik/typerep-map/issues/30):
38+ Rewrite `fromSortedList` to use `Array` and `MutableArray`
39+ instead of `IntMap`.
40+
41+## 0.2.0
42+
43+* [#43](https://github.com/kowainik/typerep-map/issues/43):
44+ Implement `IsList` instance for `TypeRepMap`.
45+ Add `hoistA` function.
46+* [#39](https://github.com/kowainik/typerep-map/issues/39):
47+ Implement `hoistWithKey` function.
48+ Add `map` function for `TMap`.
49+* Drop support for `ghc-8.0.2`.
50+
51+## 0.1.0
852
953* Initially created.
1054
1155[1]: https://pvp.haskell.org
12−[2]: https://github.com/kowainik/typerep-map/blob/master/CHANGELOG.md
56+[2]: https://github.com/kowainik/typerep-map/releases
LICENSE+373−21
@@ -1,21 +1,373 @@
1−MIT License
2−
3−Copyright (c) 2017 Veronika Romashkina
4−
5−Permission is hereby granted, free of charge, to any person obtaining a copy
6−of this software and associated documentation files (the "Software"), to deal
7−in the Software without restriction, including without limitation the rights
8−to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9−copies of the Software, and to permit persons to whom the Software is
10−furnished to do so, subject to the following conditions:
11−
12−The above copyright notice and this permission notice shall be included in all
13−copies or substantial portions of the Software.
14−
15−THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16−IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17−FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18−AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19−LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20−OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21−SOFTWARE.
1+Mozilla Public License Version 2.0
2+==================================
3+
4+1. Definitions
5+--------------
6+
7+1.1. "Contributor"
8+ means each individual or legal entity that creates, contributes to
9+ the creation of, or owns Covered Software.
10+
11+1.2. "Contributor Version"
12+ means the combination of the Contributions of others (if any) used
13+ by a Contributor and that particular Contributor's Contribution.
14+
15+1.3. "Contribution"
16+ means Covered Software of a particular Contributor.
17+
18+1.4. "Covered Software"
19+ means Source Code Form to which the initial Contributor has attached
20+ the notice in Exhibit A, the Executable Form of such Source Code
21+ Form, and Modifications of such Source Code Form, in each case
22+ including portions thereof.
23+
24+1.5. "Incompatible With Secondary Licenses"
25+ means
26+
27+ (a) that the initial Contributor has attached the notice described
28+ in Exhibit B to the Covered Software; or
29+
30+ (b) that the Covered Software was made available under the terms of
31+ version 1.1 or earlier of the License, but not also under the
32+ terms of a Secondary License.
33+
34+1.6. "Executable Form"
35+ means any form of the work other than Source Code Form.
36+
37+1.7. "Larger Work"
38+ means a work that combines Covered Software with other material, in
39+ a separate file or files, that is not Covered Software.
40+
41+1.8. "License"
42+ means this document.
43+
44+1.9. "Licensable"
45+ means having the right to grant, to the maximum extent possible,
46+ whether at the time of the initial grant or subsequently, any and
47+ all of the rights conveyed by this License.
48+
49+1.10. "Modifications"
50+ means any of the following:
51+
52+ (a) any file in Source Code Form that results from an addition to,
53+ deletion from, or modification of the contents of Covered
54+ Software; or
55+
56+ (b) any new file in Source Code Form that contains any Covered
57+ Software.
58+
59+1.11. "Patent Claims" of a Contributor
60+ means any patent claim(s), including without limitation, method,
61+ process, and apparatus claims, in any patent Licensable by such
62+ Contributor that would be infringed, but for the grant of the
63+ License, by the making, using, selling, offering for sale, having
64+ made, import, or transfer of either its Contributions or its
65+ Contributor Version.
66+
67+1.12. "Secondary License"
68+ means either the GNU General Public License, Version 2.0, the GNU
69+ Lesser General Public License, Version 2.1, the GNU Affero General
70+ Public License, Version 3.0, or any later versions of those
71+ licenses.
72+
73+1.13. "Source Code Form"
74+ means the form of the work preferred for making modifications.
75+
76+1.14. "You" (or "Your")
77+ means an individual or a legal entity exercising rights under this
78+ License. For legal entities, "You" includes any entity that
79+ controls, is controlled by, or is under common control with You. For
80+ purposes of this definition, "control" means (a) the power, direct
81+ or indirect, to cause the direction or management of such entity,
82+ whether by contract or otherwise, or (b) ownership of more than
83+ fifty percent (50%) of the outstanding shares or beneficial
84+ ownership of such entity.
85+
86+2. License Grants and Conditions
87+--------------------------------
88+
89+2.1. Grants
90+
91+Each Contributor hereby grants You a world-wide, royalty-free,
92+non-exclusive license:
93+
94+(a) under intellectual property rights (other than patent or trademark)
95+ Licensable by such Contributor to use, reproduce, make available,
96+ modify, display, perform, distribute, and otherwise exploit its
97+ Contributions, either on an unmodified basis, with Modifications, or
98+ as part of a Larger Work; and
99+
100+(b) under Patent Claims of such Contributor to make, use, sell, offer
101+ for sale, have made, import, and otherwise transfer either its
102+ Contributions or its Contributor Version.
103+
104+2.2. Effective Date
105+
106+The licenses granted in Section 2.1 with respect to any Contribution
107+become effective for each Contribution on the date the Contributor first
108+distributes such Contribution.
109+
110+2.3. Limitations on Grant Scope
111+
112+The licenses granted in this Section 2 are the only rights granted under
113+this License. No additional rights or licenses will be implied from the
114+distribution or licensing of Covered Software under this License.
115+Notwithstanding Section 2.1(b) above, no patent license is granted by a
116+Contributor:
117+
118+(a) for any code that a Contributor has removed from Covered Software;
119+ or
120+
121+(b) for infringements caused by: (i) Your and any other third party's
122+ modifications of Covered Software, or (ii) the combination of its
123+ Contributions with other software (except as part of its Contributor
124+ Version); or
125+
126+(c) under Patent Claims infringed by Covered Software in the absence of
127+ its Contributions.
128+
129+This License does not grant any rights in the trademarks, service marks,
130+or logos of any Contributor (except as may be necessary to comply with
131+the notice requirements in Section 3.4).
132+
133+2.4. Subsequent Licenses
134+
135+No Contributor makes additional grants as a result of Your choice to
136+distribute the Covered Software under a subsequent version of this
137+License (see Section 10.2) or under the terms of a Secondary License (if
138+permitted under the terms of Section 3.3).
139+
140+2.5. Representation
141+
142+Each Contributor represents that the Contributor believes its
143+Contributions are its original creation(s) or it has sufficient rights
144+to grant the rights to its Contributions conveyed by this License.
145+
146+2.6. Fair Use
147+
148+This License is not intended to limit any rights You have under
149+applicable copyright doctrines of fair use, fair dealing, or other
150+equivalents.
151+
152+2.7. Conditions
153+
154+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155+in Section 2.1.
156+
157+3. Responsibilities
158+-------------------
159+
160+3.1. Distribution of Source Form
161+
162+All distribution of Covered Software in Source Code Form, including any
163+Modifications that You create or to which You contribute, must be under
164+the terms of this License. You must inform recipients that the Source
165+Code Form of the Covered Software is governed by the terms of this
166+License, and how they can obtain a copy of this License. You may not
167+attempt to alter or restrict the recipients' rights in the Source Code
168+Form.
169+
170+3.2. Distribution of Executable Form
171+
172+If You distribute Covered Software in Executable Form then:
173+
174+(a) such Covered Software must also be made available in Source Code
175+ Form, as described in Section 3.1, and You must inform recipients of
176+ the Executable Form how they can obtain a copy of such Source Code
177+ Form by reasonable means in a timely manner, at a charge no more
178+ than the cost of distribution to the recipient; and
179+
180+(b) You may distribute such Executable Form under the terms of this
181+ License, or sublicense it under different terms, provided that the
182+ license for the Executable Form does not attempt to limit or alter
183+ the recipients' rights in the Source Code Form under this License.
184+
185+3.3. Distribution of a Larger Work
186+
187+You may create and distribute a Larger Work under terms of Your choice,
188+provided that You also comply with the requirements of this License for
189+the Covered Software. If the Larger Work is a combination of Covered
190+Software with a work governed by one or more Secondary Licenses, and the
191+Covered Software is not Incompatible With Secondary Licenses, this
192+License permits You to additionally distribute such Covered Software
193+under the terms of such Secondary License(s), so that the recipient of
194+the Larger Work may, at their option, further distribute the Covered
195+Software under the terms of either this License or such Secondary
196+License(s).
197+
198+3.4. Notices
199+
200+You may not remove or alter the substance of any license notices
201+(including copyright notices, patent notices, disclaimers of warranty,
202+or limitations of liability) contained within the Source Code Form of
203+the Covered Software, except that You may alter any license notices to
204+the extent required to remedy known factual inaccuracies.
205+
206+3.5. Application of Additional Terms
207+
208+You may choose to offer, and to charge a fee for, warranty, support,
209+indemnity or liability obligations to one or more recipients of Covered
210+Software. However, You may do so only on Your own behalf, and not on
211+behalf of any Contributor. You must make it absolutely clear that any
212+such warranty, support, indemnity, or liability obligation is offered by
213+You alone, and You hereby agree to indemnify every Contributor for any
214+liability incurred by such Contributor as a result of warranty, support,
215+indemnity or liability terms You offer. You may include additional
216+disclaimers of warranty and limitations of liability specific to any
217+jurisdiction.
218+
219+4. Inability to Comply Due to Statute or Regulation
220+---------------------------------------------------
221+
222+If it is impossible for You to comply with any of the terms of this
223+License with respect to some or all of the Covered Software due to
224+statute, judicial order, or regulation then You must: (a) comply with
225+the terms of this License to the maximum extent possible; and (b)
226+describe the limitations and the code they affect. Such description must
227+be placed in a text file included with all distributions of the Covered
228+Software under this License. Except to the extent prohibited by statute
229+or regulation, such description must be sufficiently detailed for a
230+recipient of ordinary skill to be able to understand it.
231+
232+5. Termination
233+--------------
234+
235+5.1. The rights granted under this License will terminate automatically
236+if You fail to comply with any of its terms. However, if You become
237+compliant, then the rights granted under this License from a particular
238+Contributor are reinstated (a) provisionally, unless and until such
239+Contributor explicitly and finally terminates Your grants, and (b) on an
240+ongoing basis, if such Contributor fails to notify You of the
241+non-compliance by some reasonable means prior to 60 days after You have
242+come back into compliance. Moreover, Your grants from a particular
243+Contributor are reinstated on an ongoing basis if such Contributor
244+notifies You of the non-compliance by some reasonable means, this is the
245+first time You have received notice of non-compliance with this License
246+from such Contributor, and You become compliant prior to 30 days after
247+Your receipt of the notice.
248+
249+5.2. If You initiate litigation against any entity by asserting a patent
250+infringement claim (excluding declaratory judgment actions,
251+counter-claims, and cross-claims) alleging that a Contributor Version
252+directly or indirectly infringes any patent, then the rights granted to
253+You by any and all Contributors for the Covered Software under Section
254+2.1 of this License shall terminate.
255+
256+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257+end user license agreements (excluding distributors and resellers) which
258+have been validly granted by You or Your distributors under this License
259+prior to termination shall survive termination.
260+
261+************************************************************************
262+* *
263+* 6. Disclaimer of Warranty *
264+* ------------------------- *
265+* *
266+* Covered Software is provided under this License on an "as is" *
267+* basis, without warranty of any kind, either expressed, implied, or *
268+* statutory, including, without limitation, warranties that the *
269+* Covered Software is free of defects, merchantable, fit for a *
270+* particular purpose or non-infringing. The entire risk as to the *
271+* quality and performance of the Covered Software is with You. *
272+* Should any Covered Software prove defective in any respect, You *
273+* (not any Contributor) assume the cost of any necessary servicing, *
274+* repair, or correction. This disclaimer of warranty constitutes an *
275+* essential part of this License. No use of any Covered Software is *
276+* authorized under this License except under this disclaimer. *
277+* *
278+************************************************************************
279+
280+************************************************************************
281+* *
282+* 7. Limitation of Liability *
283+* -------------------------- *
284+* *
285+* Under no circumstances and under no legal theory, whether tort *
286+* (including negligence), contract, or otherwise, shall any *
287+* Contributor, or anyone who distributes Covered Software as *
288+* permitted above, be liable to You for any direct, indirect, *
289+* special, incidental, or consequential damages of any character *
290+* including, without limitation, damages for lost profits, loss of *
291+* goodwill, work stoppage, computer failure or malfunction, or any *
292+* and all other commercial damages or losses, even if such party *
293+* shall have been informed of the possibility of such damages. This *
294+* limitation of liability shall not apply to liability for death or *
295+* personal injury resulting from such party's negligence to the *
296+* extent applicable law prohibits such limitation. Some *
297+* jurisdictions do not allow the exclusion or limitation of *
298+* incidental or consequential damages, so this exclusion and *
299+* limitation may not apply to You. *
300+* *
301+************************************************************************
302+
303+8. Litigation
304+-------------
305+
306+Any litigation relating to this License may be brought only in the
307+courts of a jurisdiction where the defendant maintains its principal
308+place of business and such litigation shall be governed by laws of that
309+jurisdiction, without reference to its conflict-of-law provisions.
310+Nothing in this Section shall prevent a party's ability to bring
311+cross-claims or counter-claims.
312+
313+9. Miscellaneous
314+----------------
315+
316+This License represents the complete agreement concerning the subject
317+matter hereof. If any provision of this License is held to be
318+unenforceable, such provision shall be reformed only to the extent
319+necessary to make it enforceable. Any law or regulation which provides
320+that the language of a contract shall be construed against the drafter
321+shall not be used to construe this License against a Contributor.
322+
323+10. Versions of the License
324+---------------------------
325+
326+10.1. New Versions
327+
328+Mozilla Foundation is the license steward. Except as provided in Section
329+10.3, no one other than the license steward has the right to modify or
330+publish new versions of this License. Each version will be given a
331+distinguishing version number.
332+
333+10.2. Effect of New Versions
334+
335+You may distribute the Covered Software under the terms of the version
336+of the License under which You originally received the Covered Software,
337+or under the terms of any subsequent version published by the license
338+steward.
339+
340+10.3. Modified Versions
341+
342+If you create software not governed by this License, and you want to
343+create a new license for such software, you may create and use a
344+modified version of this License if you rename the license and remove
345+any references to the name of the license steward (except to note that
346+such modified license differs from this License).
347+
348+10.4. Distributing Source Code Form that is Incompatible With Secondary
349+Licenses
350+
351+If You choose to distribute Source Code Form that is Incompatible With
352+Secondary Licenses under the terms of this version of the License, the
353+notice described in Exhibit B of this License must be attached.
354+
355+Exhibit A - Source Code Form License Notice
356+-------------------------------------------
357+
358+ This Source Code Form is subject to the terms of the Mozilla Public
359+ License, v. 2.0. If a copy of the MPL was not distributed with this
360+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
361+
362+If it is not possible or desirable to put the notice in a particular
363+file, then You may include the notice in a location (such as a LICENSE
364+file in a relevant directory) where a recipient would be likely to look
365+for such a notice.
366+
367+You may add additional accurate notices of copyright ownership.
368+
369+Exhibit B - "Incompatible With Secondary Licenses" Notice
370+---------------------------------------------------------
371+
372+ This Source Code Form is "Incompatible With Secondary Licenses", as
373+ defined by the Mozilla Public License, v. 2.0.
README.md+18−7
@@ -1,11 +1,22 @@
11# typerep-map
22
3−[![Hackage](https://img.shields.io/hackage/v/typerep-map.svg)](https://hackage.haskell.org/package/typerep-map)
4−[![Build status](https://secure.travis-ci.org/kowainik/typerep-map.svg)](https://travis-ci.org/kowainik/typerep-map)
5−[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vrom911/typerep-map/blob/master/LICENSE)
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)
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=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)
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)
11+[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](LICENSE)
12+
613
714`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.
815
16+For the more details on the implementation see the following blog post:
17+
18+* [typerep-map step by step](https://kowainik.github.io/posts/2018-07-11-typerep-map-step-by-step)
19+
920## Usage example
1021
1122```haskell
@@ -44,10 +55,10 @@ False
4455Tables below contain comparision with `DMap TypeRep` of ten `lookup` operations
4556on structure with size `10^4`:
4657
47−| | ghc-8.2.2 | ghc-8.4.3 |
48−|----------------|-----------|-----------|
49−| `DMap TypeRep` | 517.5 ns | 779.9 ns |
50−| `typerep-map` | 205.3 ns | 187.2 ns |
58+| | ghc-8.2.2 | ghc-8.4.3 | ghc-8.8.3 | ghc-8.10.1 |
59+|----------------|-----------|-----------|-----------|------------|
60+| `DMap TypeRep` | 517.5 ns | 779.9 ns | 1.559 μs | 1.786 μs |
61+| `typerep-map` | 205.3 ns | 187.2 ns | 190.1 ns | 169.1 ns |
5162
5263 ghc-8.2.2 | ghc-8.4.3
5364:---------:|:-----------:
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+53−33
@@ -1,57 +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− ( benchMap
13− , prepareBenchMap
14− ) where
15−
16−import Criterion.Main (Benchmark, bench, bgroup, nf)
10+ ( spec
11+ ) where
1712
1813import Prelude hiding (lookup)
1914
20−import Control.DeepSeq (rnf)
21−import Control.Exception
15+import Criterion.Main (bench, env, nf, whnf)
16+import Data.Kind (Type)
2217import Data.Maybe (fromJust)
2318import Data.Proxy (Proxy (..))
2419import Data.Typeable (Typeable)
25−import GHC.TypeLits
20+import GHC.TypeLits (type (+), KnownNat, Nat)
2621
27−import Data.TypeRep.CMap (TypeRepMap (..), empty, insert, keys, lookup)
22+import Data.TypeRep.CMap (TypeRepMap (..), empty, insert, lookup)
2823
29−benchMap :: Benchmark
30−benchMap = bgroup "map"
31− [ bench "lookup" $ nf tenLookups bigMap
32− --, bench "insert new" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 9999999999)
33− --, bench "update old" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 1)
34− ]
24+import Spec (BenchSpec (..))
3525
36−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
37− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
38− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
39− )
26+
27+spec :: BenchSpec
28+spec = 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+ }
44+
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+ )
4050tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
4151 where
4252 lp :: forall (a::Nat). Typeable a => Proxy a
4353 lp = fromJust $ lookup tmap
4454
45−-- TypeRepMap of 10000 elements
46−bigMap :: TypeRepMap (Proxy :: Nat -> *)
47−bigMap = buildBigMap 10000 (Proxy :: Proxy 0) empty
55+inserts
56+ :: forall a . (KnownNat a)
57+ => TypeRepMap (Proxy :: Nat -> Type)
58+ -> Int
59+ -> Proxy (a :: Nat)
60+ -> TypeRepMap (Proxy :: Nat -> Type)
61+inserts !c 0 _ = c
62+inserts !c n x = inserts
63+ (insert x c)
64+ (n-1)
65+ (Proxy :: Proxy (a+1))
66+
67+mkMap :: Int -> IO (TypeRepMap (Proxy :: Nat -> Type))
68+mkMap n = pure $ buildBigMap n (Proxy :: Proxy 0) empty
4869
49−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)
5076buildBigMap 1 x = insert x
5177buildBigMap n x = insert x . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
52−
53−rknf :: TypeRepMap f -> ()
54−rknf = rnf . keys
55−
56−prepareBenchMap :: IO ()
57−prepareBenchMap = evaluate (rknf bigMap)
benchmark/CacheMap.hs+59−32
@@ -1,52 +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− ( benchCacheMap
13− ) where
14−
15−import Criterion.Main (Benchmark, bench, bgroup, nf)
10+ ( spec
11+ ) where
1612
1713import Prelude hiding (lookup)
1814
15+import Criterion.Main (bench, env, nf, whnf)
16+import Data.Kind (Type)
1917import Data.Maybe (fromJust)
2018import Data.Proxy (Proxy (..))
2119import Data.Typeable (Typeable)
22−import GHC.TypeLits
20+import GHC.Exts (fromList)
21+import GHC.TypeLits (type (+), KnownNat, Nat)
2322
24−import Data.TypeRepMap.Internal (TF (..), TypeRepMap (..), fromList, lookup)
23+import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), empty, insert, lookup)
2524
26−benchCacheMap :: Benchmark
27−benchCacheMap = bgroup "vector optimal cache"
28− [ bench "lookup" $ nf tenLookups bigMap
29− -- , bench "insert new" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 9999999999)
30− -- , bench "update old" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 1)
31− ]
25+import Spec (BenchSpec (..))
3226
33−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
34− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
35− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
36− )
27+
28+spec :: BenchSpec
29+spec = 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+ }
45+
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+ )
3751tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
3852 where
39− lp :: forall (a::Nat). Typeable a => Proxy a
53+ lp :: forall (a :: Nat) . Typeable a => Proxy a
4054 lp = fromJust $ lookup tmap
4155
42−-- TypeRepMap of 10000 elements
43−bigMap :: TypeRepMap (Proxy :: Nat -> *)
44−bigMap = fromList $ buildBigMap 10000 (Proxy :: Proxy 0) []
45−
46−buildBigMap :: forall a . (KnownNat a)
47− => Int
48− -> Proxy (a :: Nat)
49− -> [TF (Proxy :: Nat -> *)]
50− -> [TF (Proxy :: Nat -> *)]
51−buildBigMap 1 x = (TF x :)
52−buildBigMap n x = (TF x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
56+inserts
57+ :: forall a . (KnownNat a)
58+ => TypeRepMap (Proxy :: Nat -> Type)
59+ -> Int
60+ -> Proxy (a :: Nat)
61+ -> TypeRepMap (Proxy :: Nat -> Type)
62+inserts !c 0 _ = c
63+inserts !c n x = inserts
64+ (insert x c)
65+ (n-1)
66+ (Proxy :: Proxy (a + 1))
67+
68+mkMap :: Int -> IO (TypeRepMap (Proxy :: Nat -> Type))
69+mkMap n = pure $ fromList $ buildBigMap n (Proxy :: Proxy 0) []
70+
71+
72+buildBigMap
73+ :: forall a . (KnownNat a)
74+ => Int
75+ -> Proxy (a :: Nat)
76+ -> [WrapTypeable (Proxy :: Nat -> Type)]
77+ -> [WrapTypeable (Proxy :: Nat -> Type)]
78+buildBigMap 1 x = (WrapTypeable x :)
79+buildBigMap n x = (WrapTypeable x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
benchmark/DMap.hs+56−53
@@ -1,86 +1,89 @@
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 #-}
9−{-# LANGUAGE ViewPatterns #-}
10−
11−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver -fno-warn-orphans #-}
129
1310module DMap
14− ( benchDMap
15− , prepareBenchDMap
16− ) where
17−
18−import Criterion.Main (Benchmark, bench, bgroup, nf)
11+ ( spec
12+ ) where
1913
2014import Prelude hiding (lookup)
2115
22−import Control.DeepSeq (rnf)
23−import Control.Exception
16+import Control.DeepSeq (NFData (..))
17+import Criterion.Main (bench, env, nf, whnf)
18+import Data.Kind (Type)
2419import Data.Maybe (fromJust)
2520import Data.Proxy (Proxy (..))
26−import Data.Type.Equality ((:~:) (..))
27−import GHC.TypeLits
21+import GHC.TypeLits (type (+), KnownNat, Nat)
2822import Type.Reflection (TypeRep, Typeable, typeRep)
2923import Type.Reflection.Unsafe (typeRepFingerprint)
30−import Unsafe.Coerce (unsafeCoerce)
3124
3225import Data.Dependent.Map (DMap, empty, insert, keys, lookup)
33−import Data.GADT.Compare (GCompare (..), GEq (..), GOrdering (..))
34−import Data.Some (Some (This))
26+import Data.Some (Some (Some))
27+
28+import Spec (BenchSpec (..))
29+
3530
3631type TypeRepMap = DMap TypeRep
3732
38−benchDMap :: Benchmark
39−benchDMap = bgroup "dependent map"
40− [ bench "lookup" $ nf tenLookups bigMap
41− -- , bench "insert new" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 9999999999)
42− -- , bench "update old" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 1)
43− ]
33+spec :: BenchSpec
34+spec = BenchSpec
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+ }
4446
45−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
46− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
47− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
48− )
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+ )
4952tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
5053 where
5154 lp :: forall (a :: Nat) . Typeable a => Proxy a
5255 lp = fromJust $ lookup (typeRep @a) tmap
5356
57+inserts
58+ :: forall a . (KnownNat a)
59+ => TypeRepMap (Proxy :: Nat -> Type)
60+ -> Int
61+ -> Proxy (a :: Nat)
62+ -> TypeRepMap (Proxy :: Nat -> Type)
63+inserts !c 0 _ = c
64+inserts !c n x = inserts
65+ (insert (typeRep @ a) x c)
66+ (n-1)
67+ (Proxy :: Proxy (a+1))
68+
5469-- TypeRepMap of 10000 elements
55−bigMap :: TypeRepMap (Proxy :: Nat -> *)
56−bigMap = buildBigMap 10000 (Proxy :: Proxy 0) empty
70+mkBigMap :: IO (DMapNF (Proxy :: Nat -> Type))
71+mkBigMap = pure . DMapNF $ buildBigMap 10000 (Proxy :: Proxy 0) empty
5772
58−buildBigMap :: forall a . (KnownNat a)
59− => Int
60− -> Proxy (a :: Nat)
61− -> TypeRepMap (Proxy :: Nat -> *)
62− -> 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)
6379buildBigMap 1 x = insert (typeRep @a) x
6480buildBigMap n x = insert (typeRep @a) x
6581 . buildBigMap (n - 1) (Proxy @(a + 1))
6682
67−rknf :: TypeRepMap f -> ()
68−rknf = rnf . map (\(This t) -> typeRepFingerprint t) . keys
69−
70−prepareBenchDMap :: IO ()
71−prepareBenchDMap = evaluate (rknf bigMap)
72−
73−instance GEq TypeRep where
74− geq :: TypeRep a -> TypeRep b -> Maybe (a :~: b)
75− geq (typeRepFingerprint -> a) (typeRepFingerprint -> b) =
76− if a == b
77− then Just $ unsafeCoerce Refl
78− else Nothing
83+-- | Wrapper that provides NFData instance to the 'DMap' structure.
84+newtype DMapNF f = DMapNF (TypeRepMap f)
7985
80−instance GCompare TypeRep where
81− gcompare :: TypeRep a -> TypeRep b -> GOrdering a b
82− gcompare (typeRepFingerprint -> a) (typeRepFingerprint -> b) =
83− case compare a b of
84− EQ -> unsafeCoerce GEQ
85− LT -> GLT
86− GT -> GGT
86+instance NFData (DMapNF f) where
87+ rnf :: DMapNF f -> ()
88+ rnf (DMapNF x) =
89+ rnf . map (\(Some t) -> typeRepFingerprint t) $ keys x
benchmark/Main.hs+41−20
@@ -1,31 +1,52 @@
11{-# LANGUAGE CPP #-}
22
3−module Main where
3+module Main
4+ ( main
5+ ) where
46
5−import Criterion.Main (defaultMain)
7+import Criterion.Main (bgroup, defaultMain)
68
7−import CacheMap (benchCacheMap)
8−import CMap (benchMap, prepareBenchMap)
9+import Spec (BenchSpec (..))
10+
11+import qualified CacheMap
12+import qualified CMap
913#if ( __GLASGOW_HASKELL__ >= 802 )
10−import DMap (benchDMap, prepareBenchDMap)
14+import qualified DMap
1115#endif
12−import OptimalVector (benchVectorOpt, prepareBenchVectorOpt)
13−--import Vector (benchVector, prepareBenchVector)
16+import qualified OptimalVector as OptVec
17+
1418
1519main :: IO ()
1620main = do
17− prepareBenchMap
18− --prepareBenchVector
19− prepareBenchVectorOpt
20−#if ( __GLASGOW_HASKELL__ >= 802 )
21− prepareBenchDMap
22−#endif
23− defaultMain
24− [ benchMap
25− -- , benchVector
26− , benchCacheMap
27− , benchVectorOpt
21+ let specs = [("CMap", CMap.spec)
22+ ,("CacheMap", CacheMap.spec)
2823#if ( __GLASGOW_HASKELL__ >= 802 )
29− , benchDMap
24+ , ("DMap", DMap.spec)
3025#endif
31− ]
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+ ]
52+ ]
benchmark/OptimalVector.hs+34−36
@@ -1,62 +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
13− ( benchVectorOpt
14− , prepareBenchVectorOpt
11+ ( spec
1512 ) where
1613
17−import Criterion.Main (Benchmark, bench, bgroup, nf)
18−
1914import Prelude hiding (lookup)
2015
21−import Control.DeepSeq (rnf)
22−import Control.Exception
16+import Criterion.Main (bench, env, nf)
17+import Data.Kind (Type)
2318import Data.Maybe (fromJust)
2419import Data.Proxy (Proxy (..))
2520import Data.Typeable (Typeable)
26−import GHC.TypeLits
21+import GHC.TypeLits (type (+), KnownNat, Nat)
2722
2823import Data.TypeRep.OptimalVector (TF (..), TypeRepMap (..), fromList, lookup)
2924
30−benchVectorOpt :: Benchmark
31−benchVectorOpt = bgroup "vector optimal"
32− [ bench "lookup" $ nf tenLookups bigMap
33− -- , bench "insert new" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 9999999999)
34− -- , bench "update old" $ whnf (\x -> rknf $ insert x bigMap) (Proxy :: Proxy 1)
35− ]
36−
37−tenLookups :: TypeRepMap (Proxy :: Nat -> *)
38− -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
39− , Proxy 50, Proxy 60, Proxy 70, Proxy 80
40− )
25+import Spec (BenchSpec (..))
26+
27+
28+spec :: BenchSpec
29+spec = 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
40+ :: TypeRepMap (Proxy :: Nat -> Type)
41+ -> ( Proxy 10, Proxy 20, Proxy 30, Proxy 40
42+ , Proxy 50, Proxy 60, Proxy 70, Proxy 80
43+ )
4144tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
4245 where
4346 lp :: forall (a::Nat). Typeable a => Proxy a
4447 lp = fromJust $ lookup tmap
4548
4649-- TypeRepMap of 10000 elements
47−bigMap :: TypeRepMap (Proxy :: Nat -> *)
48−bigMap = fromList $ buildBigMap 10000 (Proxy :: Proxy 0) []
49−
50−buildBigMap :: forall a . (KnownNat a)
51− => Int
52− -> Proxy (a :: Nat)
53− -> [TF (Proxy :: Nat -> *)]
54− -> [TF (Proxy :: Nat -> *)]
50+mkBigMap :: IO (TypeRepMap (Proxy :: Nat -> Type))
51+mkBigMap = pure $ fromList $ buildBigMap 10000 (Proxy :: Proxy 0) []
52+
53+buildBigMap
54+ :: forall a . (KnownNat a)
55+ => Int
56+ -> Proxy (a :: Nat)
57+ -> [TF (Proxy :: Nat -> Type)]
58+ -> [TF (Proxy :: Nat -> Type)]
5559buildBigMap 1 x = (TF x :)
5660buildBigMap n x = (TF x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
57−
58−rknf :: TypeRepMap f -> ()
59−rknf tVect = rnf (fingerprintAs tVect, fingerprintBs tVect)
60−
61−prepareBenchVectorOpt :: IO ()
62−prepareBenchVectorOpt = evaluate (rknf bigMap)
benchmark/Spec.hsnew file+64
@@ -0,0 +1,64 @@
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+-}
7+module Spec
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+-}
22+data BenchSpec = BenchSpec
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:
39+
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
shell.nixdeleted−10
@@ -1,10 +0,0 @@
1−# { stdenv, pkgs, haskell }:
2−with import <nixpkgs> { };
3−
4−stdenv.mkDerivation rec {
5− name = "typerep-map";
6− buildInputs = [
7− haskell.compiler.ghc843
8− pkgs.cabal-install
9− ];
10−}
src/Data/TMap.hs+75−3
@@ -2,6 +2,10 @@
22{-# LANGUAGE Rank2Types #-}
33
44{- |
5+Copyright: (c) 2017-2020 Kowainik
6+SPDX-License-Identifier: MPL-2.0
7+Maintainer: Kowainik <xrom.xkov@gmail.com>
8+
59'TMap' is a heterogeneous data structure similar in its essence to
610'Data.Map.Map' with types as keys, where each value has the type of its key.
711
@@ -33,18 +37,27 @@ module Data.TMap
3337 , delete
3438 , unionWith
3539 , union
40+ , intersectionWith
41+ , intersection
42+ , map
43+ , adjust
44+ , alter
3645
3746 -- * Query
3847 , lookup
3948 , member
4049 , size
50+ , keys
51+ , keysWith
52+ , toListWith
4153 ) where
4254
43−import Prelude hiding (lookup)
55+import Prelude hiding (lookup, map)
4456
4557import Data.Functor.Identity (Identity (..))
4658import Data.Typeable (Typeable)
4759import GHC.Exts (coerce)
60+import Type.Reflection (SomeTypeRep, TypeRep)
4861
4962import qualified Data.TypeRepMap as F
5063
@@ -79,6 +92,7 @@ one x = coerce (F.one @a @Identity $ coerce x)
7992{- |
8093
8194Insert a value into a 'TMap'.
95+TMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
8296
8397prop> size (insert v tm) >= size tm
8498prop> member @a (insert (x :: a) tm) == True
@@ -90,6 +104,9 @@ insert x = coerce (F.insert @a @Identity $ coerce x)
90104
91105{- | Delete a value from a 'TMap'.
92106
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+
93110prop> size (delete @a tm) <= size tm
94111prop> member @a (delete @a tm) == False
95112
@@ -106,10 +123,10 @@ delete = F.delete @a @Identity
106123{-# INLINE delete #-}
107124
108125-- | The union of two 'TMap's using a combining function.
109−unionWith :: (forall x. x -> x -> x) -> TMap -> TMap -> TMap
126+unionWith :: (forall x. Typeable x => x -> x -> x) -> TMap -> TMap -> TMap
110127unionWith f = F.unionWith fId
111128 where
112− fId :: forall y . Identity y -> Identity y -> Identity y
129+ fId :: forall y . Typeable y => Identity y -> Identity y -> Identity y
113130 fId y1 y2 = Identity $ f (coerce y1) (coerce y2)
114131{-# INLINE unionWith #-}
115132
@@ -119,6 +136,24 @@ union :: TMap -> TMap -> TMap
119136union = F.union
120137{-# INLINE union #-}
121138
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+
122157{- | Lookup a value of the given type in a 'TMap'.
123158
124159>>> x = lookup $ insert (11 :: Int) empty
@@ -146,3 +181,40 @@ member = F.member @a @Identity
146181size :: TMap -> Int
147182size = F.size
148183{-# INLINE size #-}
184+
185+-- | Returns the list of 'SomeTypeRep's from keys.
186+keys :: TMap -> [SomeTypeRep]
187+keys = F.keys
188+{-# INLINE keys #-}
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+
200+-- | Map a function over the values.
201+map :: (forall a. Typeable a => a -> a) -> TMap -> TMap
202+map f = F.hoistWithKey (liftToIdentity f)
203+{-# INLINE map #-}
204+
205+-- | Update a value with the result of the provided function.
206+adjust :: Typeable a => (a -> a) -> TMap -> TMap
207+adjust f = F.adjust (liftToIdentity f)
208+{-# INLINE adjust #-}
209+
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
220+liftToIdentity = coerce
src/Data/TypeRepMap.hs+56−40
@@ -1,45 +1,50 @@
11{-# LANGUAGE NoImplicitPrelude #-}
22
3−-- | A version of 'Data.TMap.TMap' parametrized by an interpretation @f@. This
4−-- sort of parametrization may be familiar to users of @vinyl@ records.
5−--
6−-- @'TypeRepMap' f@ is a more efficient replacement for @DMap
7−-- 'Type.Reflection.TypeRep' f@ (where @DMap@ is from the @dependent-map@
8−-- package).
9−--
10−-- Here is an example of using 'Prelude.Maybe' as an interpretation, with a
11−-- comparison to 'Data.TMap.TMap':
12−--
13−-- @
14−-- 'Data.TMap.TMap' 'TypeRepMap' 'Prelude.Maybe'
15−-- -------------- -------------------
16−-- Int -> 5 Int -> Just 5
17−-- Bool -> True Bool -> Nothing
18−-- Char -> \'x\' Char -> Just \'x\'
19−-- @
20−--
21−-- In fact, a 'Data.TMap.TMap' is defined as 'TypeRepMap'
22−-- 'Data.Functor.Identity'.
23−--
24−-- Since 'Type.Reflection.TypeRep' is poly-kinded, the interpretation can use
25−-- any kind for the keys. For instance, we can use the 'GHC.TypeLits.Symbol'
26−-- kind to use 'TypeRepMap' as an extensible record:
27−--
28−-- @
29−-- newtype Field name = F (FType name)
30−--
31−-- type family FType (name :: Symbol) :: Type
32−-- type instance FType "radius" = Double
33−-- type instance FType "border-color" = RGB
34−-- type instance FType "border-width" = Double
35−--
36−-- 'TypeRepMap' Field
37−-- --------------------------------------
38−-- "radius" -> F 5.7
39−-- "border-color" -> F (rgb 148 0 211)
40−-- "border-width" -> F 0.5
41−-- @
42−--
3+{- |
4+Copyright: (c) 2017-2020 Kowainik
5+SPDX-License-Identifier: MPL-2.0
6+Maintainer: Kowainik <xrom.xkov@gmail.com>
7+
8+A version of 'Data.TMap.TMap' parametrized by an interpretation @f@. This
9+sort of parametrization may be familiar to users of @vinyl@ records.
10+
11+@'TypeRepMap' f@ is a more efficient replacement for @DMap
12+'Type.Reflection.TypeRep' f@ (where @DMap@ is from the @dependent-map@
13+package).
14+
15+Here is an example of using 'Prelude.Maybe' as an interpretation, with a
16+comparison to 'Data.TMap.TMap':
17+
18+@
19+ 'Data.TMap.TMap' 'TypeRepMap' 'Prelude.Maybe'
20+-------------- -------------------
21+ Int -> 5 Int -> Just 5
22+ Bool -> True Bool -> Nothing
23+ Char -> \'x\' Char -> Just \'x\'
24+@
25+
26+In fact, a 'Data.TMap.TMap' is defined as 'TypeRepMap'
27+'Data.Functor.Identity'.
28+
29+Since 'Type.Reflection.TypeRep' is poly-kinded, the interpretation can use
30+any kind for the keys. For instance, we can use the 'GHC.TypeLits.Symbol'
31+kind to use 'TypeRepMap' as an extensible record:
32+
33+@
34+newtype Field name = F (FType name)
35+
36+type family FType (name :: Symbol) :: Type
37+type instance FType "radius" = Double
38+type instance FType "border-color" = RGB
39+type instance FType "border-width" = Double
40+
41+ 'TypeRepMap' Field
42+--------------------------------------
43+ "radius" -> F 5.7
44+ "border-color" -> F (rgb 148 0 211)
45+ "border-width" -> F 0.5
46+@
47+--}
4348module Data.TypeRepMap
4449 ( -- * Map type
4550 TypeRepMap()
@@ -51,15 +56,26 @@ module Data.TypeRepMap
5156 -- * Modification
5257 , insert
5358 , delete
59+ , adjust
60+ , alter
5461 , hoist
62+ , hoistA
63+ , hoistWithKey
5564 , unionWith
5665 , union
66+ , intersectionWith
67+ , intersection
5768
5869 -- * Query
5970 , lookup
6071 , member
6172 , size
73+ , keys
74+ , keysWith
75+ , toListWith
6276
77+ -- * 'IsList'
78+ , WrapTypeable (..)
6379 ) where
6480
6581import Data.TypeRepMap.Internal
src/Data/TypeRepMap/Internal.hs+433−91
@@ -1,47 +1,69 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE BangPatterns #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE InstanceSigs #-}
6−{-# LANGUAGE KindSignatures #-}
7−{-# LANGUAGE MagicHash #-}
8−{-# LANGUAGE PolyKinds #-}
9−{-# LANGUAGE Rank2Types #-}
10−{-# LANGUAGE TypeFamilies #-}
11−{-# LANGUAGE TypeInType #-}
1+{-# OPTIONS_GHC -Wno-missing-export-lists #-}
2+
3+{-# LANGUAGE AllowAmbiguousTypes #-}
4+{-# LANGUAGE BangPatterns #-}
5+{-# LANGUAGE CPP #-}
6+{-# LANGUAGE DataKinds #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE MagicHash #-}
10+{-# LANGUAGE PolyKinds #-}
11+{-# LANGUAGE Rank2Types #-}
12+{-# LANGUAGE TypeFamilies #-}
13+{-# LANGUAGE TypeInType #-}
14+{-# LANGUAGE ViewPatterns #-}
15+
16+#if __GLASGOW_HASKELL__ >= 806
17+{-# LANGUAGE QuantifiedConstraints #-}
18+#endif
1219
1320-- {-# OPTIONS_GHC -ddump-simpl -dsuppress-idinfo -dsuppress-coercions -dsuppress-type-applications -dsuppress-uniques -dsuppress-module-prefixes #-}
1421
15−-- | Internal API for 'TypeRepMap' and operations on it. The functions here do
16−-- not have any stability guarantees and can change between minor versions.
17−--
18−-- If you need to use this module for purposes other than tests,
19−-- create an issue.
20−--
22+{- |
23+Copyright: (c) 2017-2020 Kowainik
24+SPDX-License-Identifier: MPL-2.0
25+Maintainer: Kowainik <xrom.xkov@gmail.com>
26+
27+Internal API for 'TypeRepMap' and operations on it. The functions here do
28+not have any stability guarantees and can change between minor versions.
29+
30+If you need to use this module for purposes other than tests,
31+create an issue.
32+-}
33+
34+#include "MachDeps.h"
35+
2136module Data.TypeRepMap.Internal where
2237
2338import Prelude hiding (lookup)
2439
25−import Control.Arrow ((&&&))
40+import Control.DeepSeq
41+import Control.Monad.ST (ST, runST)
42+import Control.Monad.Zip (mzip)
2643import Data.Function (on)
27−import Data.IntMap.Strict (IntMap)
2844import Data.Kind (Type)
29−import Data.List (nubBy)
30−import Data.Maybe (fromJust)
31−import Data.Primitive.Array (Array, indexArray, mapArray')
32−import Data.Primitive.PrimArray (PrimArray, indexPrimArray, sizeofPrimArray)
33−import Data.Proxy (Proxy (..))
34−import Data.Semigroup (Semigroup (..))
35−import Data.Typeable (Typeable, typeRep, typeRepFingerprint)
45+import Data.Type.Equality ((:~:) (..), TestEquality (..))
46+import Data.List (intercalate, nubBy)
47+import Data.Maybe (fromMaybe)
48+import Data.Primitive.Array (Array, MutableArray, indexArray, mapArray', sizeofArray,
49+ thawArray, unsafeFreezeArray, writeArray)
50+import Data.Primitive.PrimArray (writePrimArray, newPrimArray, unsafeFreezePrimArray, primArrayToList, MutablePrimArray, primArrayFromListN, PrimArray, indexPrimArray, sizeofPrimArray)
51+import Data.Semigroup (Semigroup (..), All(..))
3652import GHC.Base (Any, Int (..), Int#, (*#), (+#), (<#))
37−import GHC.Exts (inline, sortWith)
53+import GHC.Exts (IsList (..), inline, sortWith)
3854import GHC.Fingerprint (Fingerprint (..))
55+#if WORD_SIZE_IN_BITS >= 64
3956import GHC.Prim (eqWord#, ltWord#)
57+#else
58+import GHC.IntWord64 (eqWord64#, ltWord64#)
59+#define eqWord eqWord64
60+#define ltWord ltWord64
61+#endif
4062import GHC.Word (Word64 (..))
63+import Type.Reflection (SomeTypeRep (..), TypeRep, Typeable, typeRep, withTypeable)
64+import Type.Reflection.Unsafe (typeRepFingerprint)
4165import Unsafe.Coerce (unsafeCoerce)
4266
43−import qualified Data.IntMap.Strict as IM
44−import qualified Data.Map.Strict as Map
4567import qualified GHC.Exts as GHC (fromList, toList)
4668
4769{- |
@@ -69,13 +91,20 @@ data TypeRepMap (f :: k -> Type) =
6991 TypeRepMap
7092 { fingerprintAs :: {-# UNPACK #-} !(PrimArray Word64) -- ^ first components of key fingerprints
7193 , fingerprintBs :: {-# UNPACK #-} !(PrimArray Word64) -- ^ second components of key fingerprints
72− , anys :: {-# UNPACK #-} !(Array Any) -- ^ values stored in the map
94+ , trAnys :: {-# UNPACK #-} !(Array Any) -- ^ values stored in the map
95+ , trKeys :: {-# UNPACK #-} !(Array Any) -- ^ typerep keys
7396 }
7497 -- ^ an unsafe constructor for 'TypeRepMap'
7598
76−-- | Shows only 'Fingerprint's.
99+instance NFData (TypeRepMap f) where
100+ rnf x = rnf (keys x) `seq` ()
101+
102+-- | Shows only keys.
77103instance Show (TypeRepMap f) where
78− show = show . toFingerprints
104+ show TypeRepMap{..} = "TypeRepMap [" ++ showKeys ++ "]"
105+ where
106+ showKeys :: String
107+ showKeys = intercalate ", " $ toList $ mapArray' (show . anyToTypeRep) trKeys
79108
80109-- | Uses 'union' to combine 'TypeRepMap's.
81110instance Semigroup (TypeRepMap f) where
@@ -84,11 +113,36 @@ instance Semigroup (TypeRepMap f) where
84113 {-# INLINE (<>) #-}
85114
86115instance Monoid (TypeRepMap f) where
87− mempty = TypeRepMap mempty mempty mempty
116+ mempty = TypeRepMap mempty mempty mempty mempty
88117 mappend = (<>)
89118 {-# INLINE mempty #-}
90119 {-# INLINE mappend #-}
91120
121+#if __GLASGOW_HASKELL__ >= 806
122+instance (forall a. Typeable a => Eq (f a)) => Eq (TypeRepMap f) where
123+ tm1 == tm2 = size tm1 == size tm2 && go 0
124+ where
125+ go :: Int -> Bool
126+ go i
127+ | i == size tm1 = True
128+ | otherwise = case testEquality tr1i tr2i of
129+ Nothing -> False
130+ Just Refl -> repEq tr1i (fromAny tv1i) (fromAny tv2i) && go (i + 1)
131+ where
132+ tr1i :: TypeRep x
133+ tr1i = anyToTypeRep $ indexArray (trKeys tm1) i
134+
135+ tr2i :: TypeRep y
136+ tr2i = anyToTypeRep $ indexArray (trKeys tm2) i
137+
138+ tv1i, tv2i :: Any
139+ tv1i = indexArray (trAnys tm1) i
140+ tv2i = indexArray (trAnys tm2) i
141+
142+ repEq :: TypeRep x -> f x -> f x -> Bool
143+ repEq tr = withTypeable tr (==)
144+#endif
145+
92146-- | Returns the list of 'Fingerprint's from 'TypeRepMap'.
93147toFingerprints :: TypeRepMap f -> [Fingerprint]
94148toFingerprints TypeRepMap{..} =
@@ -115,33 +169,49 @@ prop> member @a (one (x :: f a)) == True
115169
116170-}
117171one :: forall a f . Typeable a => f a -> TypeRepMap f
118−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)
119178{-# INLINE one #-}
120179
121180{- |
122181
123182Insert a value into a 'TypeRepMap'.
183+TypeRepMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
124184
125185prop> size (insert v tm) >= size tm
126186prop> member @a (insert (x :: f a) tm) == True
127187
128188-}
129189insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
130−insert x = fromListPairs . addX . toPairList
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)}
131195 where
132− pairX :: (Fingerprint, Any)
133− pairX@(fpX, _) = (calcFp x, toAny x)
134−
135− addX :: [(Fingerprint, Any)] -> [(Fingerprint, Any)]
136− addX l = pairX : 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
137202{-# INLINE insert #-}
138203
139204-- Extract the kind of a type. We use it to work around lack of syntax for
140205-- inferred type variables (which are not subject to type applications).
141206type KindOf (a :: k) = k
142207
208+type ArgKindOf (f :: k -> l) = k
209+
143210{- | Delete a value from a 'TypeRepMap'.
144211
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+
145215prop> size (delete @a tm) <= size tm
146216prop> member @a (delete @a tm) == False
147217
@@ -154,9 +224,61 @@ False
154224True
155225-}
156226delete :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> TypeRepMap f
157−delete = fromListPairs . deleteByFst (typeFp @a) . toPairList
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
158234{-# INLINE delete #-}
159235
236+deleteFirst :: (a -> Bool) -> [a] -> [a]
237+deleteFirst _ [] = []
238+deleteFirst p (x : xs) = if p x then xs else x : deleteFirst p xs
239+
240+{- |
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.
243+
244+>>> trmap = fromList @(TypeRepMap Identity) [WrapTypeable $ Identity "a"]
245+>>> lookup @String $ adjust (fmap (++ "ww")) trmap
246+Just (Identity "aww")
247+-}
248+adjust :: forall a f . Typeable a => (f a -> f a) -> TypeRepMap f -> TypeRepMap f
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)}
273+ where
274+ replaceAnyAt :: Int -> Any -> Array Any -> Array Any
275+ replaceAnyAt i v trAs = runST $ do
276+ let n = sizeofArray trAs
277+ mutArr <- thawArray trAs 0 n
278+ writeArray mutArr i v
279+ unsafeFreezeArray mutArr
280+{-# INLINE alter #-}
281+
160282{- | Map over the elements of a 'TypeRepMap'.
161283
162284>>> tm = insert (Identity True) $ one (Identity 'a')
@@ -171,27 +293,107 @@ Just [True]
171293Just "a"
172294-}
173295hoist :: (forall x. f x -> g x) -> TypeRepMap f -> TypeRepMap g
174−hoist f (TypeRepMap as bs ans) = TypeRepMap as bs $ mapArray' (toAny . f . fromAny) ans
296+hoist f (TypeRepMap as bs ans ks) = TypeRepMap as bs (mapArray' (toAny . f . fromAny) ans) ks
175297{-# INLINE hoist #-}
176298
177−-- | The union of two 'TypeRepMap's using a combining function.
178−unionWith :: (forall x. f x -> f x -> f x) -> TypeRepMap f -> TypeRepMap f -> TypeRepMap f
179−unionWith f m1 m2 = fromListPairs
180− $ Map.toList
181− $ Map.unionWith combine
182− (Map.fromList $ toPairList m1)
183− (Map.fromList $ toPairList m2)
299+hoistA :: (Applicative t) => (forall x. f x -> t (g x)) -> TypeRepMap f -> t (TypeRepMap g)
300+hoistA f (TypeRepMap as bs (toList -> ans) ks) = (\l -> TypeRepMap as bs (fromList $ map toAny l) ks)
301+ <$> traverse (f . fromAny) ans
302+{-# INLINE hoistA #-}
303+
304+hoistWithKey :: forall f g. (forall x. Typeable x => f x -> g x) -> TypeRepMap f -> TypeRepMap g
305+hoistWithKey f (TypeRepMap as bs ans ks) = TypeRepMap as bs newAns ks
306+ where
307+ newAns = mapArray' mapAns (mzip ans ks)
308+ mapAns (a, k) = toAny $ withTr (unsafeCoerce k) $ fromAny a
309+
310+ withTr :: forall x. TypeRep x -> f x -> g x
311+ withTr t = withTypeable t f
312+{-# INLINE hoistWithKey #-}
313+
314+-- | The union of two 'TypeRepMap's using a combining function for conflicting entries. @O(n + m)@
315+unionWith :: forall f. (forall x. Typeable x => f x -> f x -> f x) -> TypeRepMap f -> TypeRepMap f -> TypeRepMap f
316+unionWith f ma mb = do
317+ fromSortedTriples $ mergeMaps (toSortedTriples ma) (toSortedTriples mb)
184318 where
185− combine :: Any -> Any -> Any
186− combine a b = toAny $ f (fromAny a) (fromAny b)
319+ f' :: forall x. TypeRep x -> f x -> f x -> f x
320+ f' tr = withTypeable tr f
321+
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
187347{-# INLINE unionWith #-}
188348
189−-- | 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
190350-- duplicate keys are encountered, i.e. @'union' == 'unionWith' const@.
191351union :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
192352union = unionWith const
193353{-# INLINE union #-}
194354
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+
195397{- | Check if a value of the given type is present in a 'TypeRepMap'.
196398
197399>>> member @Char $ one (Identity 'a')
@@ -214,7 +416,7 @@ Just (Identity 11)
214416Nothing
215417-}
216418lookup :: forall a f . Typeable a => TypeRepMap f -> Maybe (f a)
217−lookup tVect = fromAny . (anys tVect `indexArray`)
419+lookup tVect = fromAny . (trAnys tVect `indexArray`)
218420 <$> cachedBinarySearch (typeFp @a)
219421 (fingerprintAs tVect)
220422 (fingerprintBs tVect)
@@ -225,6 +427,24 @@ size :: TypeRepMap f -> Int
225427size = sizeofPrimArray . fingerprintAs
226428{-# INLINE size #-}
227429
430+-- | Return the list of 'SomeTypeRep' from the keys.
431+keys :: TypeRepMap f -> [SomeTypeRep]
432+keys = keysWith SomeTypeRep
433+{-# INLINE keys #-}
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+
228448-- | Binary searched based on this article
229449-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
230450-- with modification for our two-vector search case.
@@ -258,71 +478,193 @@ toAny = unsafeCoerce
258478fromAny :: Any -> f a
259479fromAny = unsafeCoerce
260480
481+anyToTypeRep :: Any -> TypeRep f
482+anyToTypeRep = unsafeCoerce
483+
261484typeFp :: forall a . Typeable a => Fingerprint
262−typeFp = typeRepFingerprint $ typeRep $ Proxy @a
485+typeFp = typeRepFingerprint $ typeRep @a
263486{-# INLINE typeFp #-}
264487
265−toPairList :: TypeRepMap f -> [(Fingerprint, Any)]
266−toPairList tm = zip (toFingerprints tm) (GHC.toList $ anys tm)
488+toTriples :: TypeRepMap f -> [(Fingerprint, Any, Any)]
489+toTriples tm = zip3 (toFingerprints tm) (GHC.toList $ trAnys tm) (GHC.toList $ trKeys tm)
267490
268−deleteByFst :: Eq a => a -> [(a, b)] -> [(a, b)]
269−deleteByFst x = filter ((/= x) . fst)
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)
505+
506+nubByFst :: (Eq a) => [(a, b, c)] -> [(a, b, c)]
507+nubByFst = nubBy ((==) `on` fst3)
270508
271−nubByFst :: (Eq a) => [(a, b)] -> [(a, b)]
272−nubByFst = nubBy ((==) `on` fst)
509+fst3 :: (a, b, c) -> a
510+fst3 (a, _, _) = a
273511
274512----------------------------------------------------------------------------
275513-- Functions for testing and benchmarking
276514----------------------------------------------------------------------------
277515
278516-- | Existential wrapper around 'Typeable' indexed by @f@ type parameter.
279−-- Useful for 'TypeRepMap' structure creation form list of 'TF's.
280−data TF f where
281− TF :: Typeable a => f a -> TF f
517+-- Useful for 'TypeRepMap' structure creation form list of 'WrapTypeable's.
518+data WrapTypeable f where
519+ WrapTypeable :: Typeable a => f a -> WrapTypeable f
282520
283−instance Show (TF f) where
284− show (TF tf) = show $ calcFp tf
521+instance Show (WrapTypeable f) where
522+ show (WrapTypeable (_ :: f a)) = show $ calcFp @a
523+
524+{- |
285525
286−{- | Creates 'TypeRepMap' from a list of 'TF's.
526+prop> fromList . toList == 'id'
527+
528+Creates 'TypeRepMap' from a list of 'WrapTypeable's.
529+
530+>>> show $ fromList [WrapTypeable $ Identity True, WrapTypeable $ Identity 'a']
531+TypeRepMap [Bool, Char]
287532
288−>>> size $ fromList [TF $ Identity True, TF $ Identity 'a']
289−2
290533
291534-}
292−fromList :: forall f . [TF f] -> TypeRepMap f
293−fromList = fromListPairs . map (fp &&& an)
294− where
295− fp :: TF f -> Fingerprint
296− fp (TF x) = calcFp x
535+instance IsList (TypeRepMap f) where
536+ type Item (TypeRepMap f) = WrapTypeable f
297537
298− an :: TF f -> Any
299− an (TF x) = toAny x
538+ fromList :: [WrapTypeable f] -> TypeRepMap f
539+ fromList = fromTriples . map (\x -> (fp x, an x, k x))
540+ where
541+ fp :: WrapTypeable f -> Fingerprint
542+ fp (WrapTypeable (_ :: f a)) = calcFp @a
300543
301−fromF :: Typeable a => f a -> Proxy a
302−fromF _ = Proxy
544+ an :: WrapTypeable f -> Any
545+ an (WrapTypeable x) = toAny x
303546
304−calcFp :: Typeable a => f a -> Fingerprint
305−calcFp = typeRepFingerprint . typeRep . fromF
547+ k :: WrapTypeable f -> Any
548+ k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
306549
307−fromListPairs :: [(Fingerprint, Any)] -> TypeRepMap f
308−fromListPairs kvs = TypeRepMap (GHC.fromList fpAs) (GHC.fromList fpBs) (GHC.fromList ans)
550+ toList :: TypeRepMap f -> [WrapTypeable f]
551+ toList = toListWith WrapTypeable
552+
553+calcFp :: forall a . Typeable a => Fingerprint
554+calcFp = typeRepFingerprint $ typeRep @a
555+
556+fromTriples :: [(Fingerprint, Any, Any)] -> TypeRepMap f
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)
309561 where
310562 (fpAs, fpBs) = unzip $ map (\(Fingerprint a b) -> (a, b)) fps
311− (fps, ans) = unzip $ fromSortedList $ sortWith fst $ nubByFst kvs
563+ (fps, ans, ks) = unzip3 $ fromSortedList kvs
312564
313565----------------------------------------------------------------------------
314566-- Tree-like conversion
315567----------------------------------------------------------------------------
316568
317569fromSortedList :: forall a . [a] -> [a]
318−fromSortedList l = IM.elems $ fst $ go 0 0 mempty (IM.fromList $ zip [0..] l)
570+fromSortedList l = runST $ do
571+ let n = length l
572+ let arrOrigin = fromListN n l
573+ arrResult <- thawArray arrOrigin 0 n
574+ go n arrResult arrOrigin
575+ toList <$> unsafeFreezeArray arrResult
319576 where
320577 -- state monad could be used here, but it's another dependency
321− go :: Int -> Int -> IntMap a -> IntMap a -> (IntMap a, Int)
322− go i first result vector =
323− if i >= IM.size vector
324− then (result, first)
325− else do
326− let (newResult, newFirst) = go (2 * i + 1) first result vector
327− let withCur = IM.insert i (fromJust $ IM.lookup newFirst vector) newResult
328− go (2 * i + 2) (newFirst + 1) withCur vector
578+ go :: forall s . Int -> MutableArray s a -> Array a -> ST s ()
579+ go len result origin = () <$ loop 0 0
580+ where
581+ loop :: Int -> Int -> ST s Int
582+ loop i first =
583+ if i >= len
584+ then pure first
585+ else do
586+ newFirst <- loop (2 * i + 1) first
587+ writeArray result i (indexArray origin newFirst)
588+ loop (2 * i + 2) (newFirst + 1)
589+
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+
614+----------------------------------------------------------------------------
615+-- Helper functions.
616+----------------------------------------------------------------------------
617+
618+-- | Check that invariant of the structure holds.
619+-- The structure maintains the following invariant.
620+-- For each element @A@ at index @i@:
621+--
622+-- 1. if there is an element @B@ at index @2*i+1@,
623+-- then @B < A@.
624+--
625+-- 2. if there is an element @C@ at index @2*i+2@,
626+-- then @A < C@.
627+--
628+invariantCheck :: TypeRepMap f -> Bool
629+invariantCheck TypeRepMap{..} = getAll (check 0)
630+ where
631+ lastMay [] = Nothing
632+ lastMay [x] = Just x
633+ lastMay (_:xs) = lastMay xs
634+ sz = sizeofPrimArray fingerprintAs
635+ check i | i >= sz = All True
636+ | otherwise =
637+ let left = i*2+1
638+ right = i*2+2
639+ -- maximum value in the left branch
640+ leftMax =
641+ fmap (\j -> (indexPrimArray fingerprintAs j, indexPrimArray fingerprintBs j))
642+ $ lastMay
643+ $ takeWhile (<sz)
644+ $ iterate (\j -> j*2+2) left
645+ -- minimum value in the right branch
646+ rightMin =
647+ fmap (\j -> (indexPrimArray fingerprintAs j, indexPrimArray fingerprintBs j))
648+ $ lastMay
649+ $ takeWhile (<sz)
650+ $ iterate (\j -> j*2+1) right
651+ in mconcat
652+ [ All $
653+ if left < sz
654+ then
655+ case indexPrimArray fingerprintAs i `compare` indexPrimArray fingerprintAs left of
656+ LT -> False
657+ EQ -> indexPrimArray fingerprintBs i >= indexPrimArray fingerprintBs left
658+ GT -> True
659+ else True
660+ , All $
661+ if right < sz
662+ then
663+ case indexPrimArray fingerprintAs i `compare` indexPrimArray fingerprintAs right of
664+ LT -> True
665+ EQ -> indexPrimArray fingerprintBs i <= indexPrimArray fingerprintBs right
666+ GT -> False
667+ else True
668+ , All $ fromMaybe True $ (<=) <$> leftMax <*> rightMin
669+ , check (i+1)
670+ ]
stack.yaml+4−2
@@ -1,4 +1,6 @@
1−resolver: nightly-2018-07-05
1+resolver: lts-15.8
22
33extra-deps:
4− - primitive-0.6.4.0
4+ - dependent-map-0.2.4.0
5+ - dependent-sum-0.5
6+ - hspec-hedgehog-0.0.1.2
test/Test.hs+21−1
@@ -1 +1,21 @@
1−{-# OPTIONS_GHC -F -pgmF tasty-discover -optF --tree-display #-}
1+module Main
2+ ( main
3+ ) where
4+
5+import Test.Hspec (hspec)
6+
7+import Test.TypeRep.CMap (cMapSpec)
8+import Test.TypeRep.TypeRepMap (typeRepMapSpec)
9+import Test.TypeRep.TypeRepMapProperty (typeRepMapPropertySpec)
10+import Test.TypeRep.Vector (vectorSpec)
11+import Test.TypeRep.VectorOpt (optimalVectorSpec)
12+
13+
14+main :: IO ()
15+main = hspec $ do
16+ typeRepMapSpec
17+ cMapSpec
18+ vectorSpec
19+ optimalVectorSpec
20+ -- property
21+ typeRepMapPropertySpec
test/Test/TypeRep/CMap.hs+9−7
@@ -1,16 +1,18 @@
1−module Test.TypeRep.CMap where
1+module Test.TypeRep.CMap
2+ ( cMapSpec
3+ ) where
24
35import Prelude hiding (lookup)
46
57import Data.Functor.Identity (Identity (..))
8+import Test.Hspec (Spec, describe, it, shouldBe)
69
7−import Test.Tasty.Hspec
10+import Data.TypeRep.CMap (TypeRepMap, empty, insert, lookup, size)
811
9−import Data.TypeRep.CMap
1012
11−-- Simple test for 'lookup', 'insert' and 'size' functions.
12−spec_insertLookup :: Spec
13−spec_insertLookup = do
13+-- | Simple test for 'lookup', 'insert' and 'size' functions.
14+cMapSpec :: Spec
15+cMapSpec = describe "Containers Map TypeRep" $ do
1416 describe "Lookup Test" $ do
1517 it "returns the inserted element" $
1618 lookup (insert (Identity 'a') empty) `shouldBe` Just (Identity 'a')
@@ -35,7 +37,7 @@ mapOf10 = insert (Identity True)
3537 $ insert (Identity $ Just ())
3638 $ insert (Identity [()])
3739 $ insert (Identity ())
38− $ insert (Identity "aaa")
40+ $ insert (Identity @String "aaa")
3941 $ insert (Identity $ Just 'a')
4042 $ insert (Identity 'a')
4143 $ insert (Identity (11 :: Int)) empty
test/Test/TypeRep/CacheMap.hsdeleted−48
@@ -1,48 +0,0 @@
1−module Test.TypeRep.CacheMap where
2−
3−import Prelude hiding (lookup)
4−
5−import Data.Functor.Identity (Identity (..))
6−import Test.Tasty.Hspec (Spec, describe, it, shouldBe)
7−
8−import Data.TypeRepMap.Internal (TF (..), fromList)
9−import Data.TMap (TMap, empty, insert, lookup, one, size, union)
10−
11−-- Simple test for 'lookup', 'insert' and 'size' functions.
12−spec_insertLookup :: Spec
13−spec_insertLookup = do
14− describe "Lookup Test" $ do
15− it "returns the inserted element" $
16− lookup (fromList [TF $ Identity 'a']) `shouldBe` Just 'a'
17− it "returns the second inserted value of the same type" $
18− lookup (fromList [TF (Identity 'b'), TF (Identity 'a')]) `shouldBe` Just 'b'
19−
20− describe "Size Test" $ do
21− it "is empty" $
22− size empty `shouldBe` 0
23− it "is of size 1 when 1 element inserted" $
24− size (one 'a') `shouldBe` 1
25− it "doesn't increase size when element of the same type is added" $
26− size (insert 'b' $ insert 'a' empty) `shouldBe` 1
27− it "returns 10 when 10 different types are inserted" $
28− size mapOf10 `shouldBe` 10
29−
30− describe "Union test" $ do
31− let m = fromList [TF $ Identity 'a', TF $ Identity True] `union` fromList [TF $ Identity 'b']
32− it "lookup works on union as expected" $ do
33− lookup m `shouldBe` Just 'a'
34− lookup m `shouldBe` Just True
35− lookup @Int m `shouldBe` Nothing
36−
37−
38−mapOf10 :: TMap
39−mapOf10 = insert True
40− $ insert [True, False]
41− $ insert (Just True)
42− $ insert (Just ())
43− $ insert [()]
44− $ insert ()
45− $ insert "aaa"
46− $ insert (Just 'a')
47− $ insert 'a'
48− $ insert (11 :: Int) empty
test/Test/TypeRep/MapProperty.hsdeleted−109
@@ -1,109 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3−{-# LANGUAGE KindSignatures #-}
4−{-# LANGUAGE PolyKinds #-}
5−
6−{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
7−
8−module Test.TypeRep.MapProperty where
9−
10−import Prelude hiding (lookup)
11−
12−import Data.Proxy (Proxy (..))
13−import Data.Semigroup (Semigroup (..))
14−import GHC.Stack (HasCallStack)
15−import GHC.TypeLits (Nat, SomeNat (..), someNatVal)
16−import Hedgehog (MonadGen, PropertyT, forAll, property, (===))
17−import Test.Tasty (TestName, TestTree)
18−import Test.Tasty.Hedgehog (testProperty)
19−
20−import Data.TypeRepMap.Internal (TF (..), TypeRepMap (..), delete, fromList, insert, lookup, member)
21−
22−import qualified Hedgehog.Gen as Gen
23−import qualified Hedgehog.Range as Range
24−
25−----------------------------------------------------------------------------
26−-- Common utils
27−----------------------------------------------------------------------------
28−
29−type PropertyTest = [TestTree]
30−
31−prop :: HasCallStack => TestName -> PropertyT IO () -> PropertyTest
32−prop testName = pure . testProperty testName . property
33−
34−----------------------------------------------------------------------------
35−-- Map modification properties
36−----------------------------------------------------------------------------
37−
38−test_InsertLookup :: PropertyTest
39−test_InsertLookup = prop "lookup k (insert k v m) == Just v" $ do
40− m <- forAll genMap
41− TF (proxy :: IntProxy n) <- forAll genTF
42−
43− lookup @n @IntProxy (insert proxy m) === Just proxy
44−
45−test_InsertInsert :: PropertyTest
46−test_InsertInsert = prop "insert k b . insert k a == insert k b" $ do
47− m <- forAll genMap
48− TF a@(IntProxy (proxy :: Proxy n) i) <- forAll genTF
49− let b = IntProxy proxy (i + 1)
50− lookup @n @IntProxy (insert b $ insert a m) === Just b
51−
52−test_DeleteMember :: PropertyTest
53−test_DeleteMember = prop "member k . delete k == False" $ do
54− m <- forAll genMap
55− TF (proxy :: IntProxy n) <- forAll genTF
56− shouldInsert <- forAll Gen.bool
57−
58− if shouldInsert then
59− member @n (delete @n $ insert proxy m) === False
60− else
61− member @n (delete @n m) === False
62−
63−----------------------------------------------------------------------------
64−-- Semigroup and Monoid laws
65−----------------------------------------------------------------------------
66−
67−-- This newtype is used to compare 'TypeRepMap's using only 'Fingerprint's. It's
68−-- not a good idea to write such `Eq` instance for `TypeRepMap` itself because
69−-- it doesn't compare values so it's not true equality. But this should be
70−-- enough for tests.
71−newtype FpMap f = FpMap (TypeRepMap f)
72− deriving (Show, Semigroup, Monoid)
73−
74−instance Eq (FpMap f) where
75− FpMap (TypeRepMap as1 bs1 _) == FpMap (TypeRepMap as2 bs2 _) =
76− as1 == as2 && bs1 == bs2
77−
78−test_SemigroupAssoc :: PropertyTest
79−test_SemigroupAssoc = prop "x <> (y <> z) == (x <> y) <> z" $ do
80− x <- FpMap <$> forAll genMap
81− y <- FpMap <$> forAll genMap
82− z <- FpMap <$> forAll genMap
83−
84− (x <> (y <> z)) === ((x <> y) <> z)
85−
86−test_MonoidIdentity :: PropertyTest
87−test_MonoidIdentity = prop "x <> mempty == mempty <> x == x" $ do
88− x <- FpMap <$> forAll genMap
89−
90− x <> mempty === x
91− mempty <> x === x
92−
93−----------------------------------------------------------------------------
94−-- Generators
95−----------------------------------------------------------------------------
96−
97−data IntProxy (n :: Nat) = IntProxy (Proxy n) Int
98− deriving (Show, Eq)
99−
100−genMap :: MonadGen m => m (TypeRepMap IntProxy)
101−genMap = fromList <$> Gen.list (Range.linear 0 1000) genTF
102−
103−genTF :: MonadGen m => m (TF IntProxy)
104−genTF = do
105− randNat :: Integer <- Gen.integral (Range.linear 0 10000)
106− randInt <- Gen.int Range.constantBounded
107− case someNatVal randNat of
108− Just (SomeNat proxyNat) -> pure $ TF $ IntProxy proxyNat randInt
109− Nothing -> error "Invalid test generator"
test/Test/TypeRep/TypeRepMap.hsnew file+111
@@ -0,0 +1,111 @@
1+module Test.TypeRep.TypeRepMap
2+ ( typeRepMapSpec
3+ ) where
4+
5+import Prelude hiding (lookup)
6+
7+import Data.Functor.Identity (Identity (..))
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)
14+
15+import Data.TMap (TMap, empty, insert, lookup, one, size, union, keys)
16+import Data.TypeRepMap.Internal (WrapTypeable (..))
17+
18+
19+-- Simple test for 'lookup', 'insert', 'size', 'keys', 'toList' functions.
20+typeRepMapSpec :: Spec
21+typeRepMapSpec = describe "TypeRepMap" $ do
22+ describe "Lookup Test" $ do
23+ it "returns the inserted element" $
24+ lookup (fromList [WrapTypeable $ Identity 'a']) `shouldBe` Just 'a'
25+ it "returns the second inserted value of the same type" $
26+ lookup (fromList [WrapTypeable (Identity 'b'), WrapTypeable (Identity 'a')]) `shouldBe` Just 'b'
27+
28+ describe "Size Test" $ do
29+ it "is empty" $
30+ size empty `shouldBe` 0
31+ it "is of size 1 when 1 element inserted" $
32+ size (one 'a') `shouldBe` 1
33+ it "doesn't increase size when element of the same type is added" $
34+ size (insert 'b' $ insert 'a' empty) `shouldBe` 1
35+ it "returns 10 when 10 different types are inserted" $
36+ size mapOf10 `shouldBe` 10
37+
38+ describe "Union test" $ do
39+ let m = fromList [WrapTypeable $ Identity 'a', WrapTypeable $ Identity True] `union`
40+ fromList [WrapTypeable $ Identity 'b']
41+ it "lookup works on union as expected" $ do
42+ lookup m `shouldBe` Just 'a'
43+ lookup m `shouldBe` Just True
44+ lookup @Int m `shouldBe` Nothing
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
100+
101+mapOf10 :: TMap
102+mapOf10 = insert True
103+ $ insert [True, False]
104+ $ insert (Just True)
105+ $ insert (Just ())
106+ $ insert [()]
107+ $ insert ()
108+ $ insert @String "aaa"
109+ $ insert (Just 'a')
110+ $ insert 'a'
111+ $ insert (11 :: Int) empty
test/Test/TypeRep/TypeRepMapProperty.hsnew file+184
@@ -0,0 +1,184 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE PolyKinds #-}
5+
6+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
7+
8+module Test.TypeRep.TypeRepMapProperty
9+ ( typeRepMapPropertySpec
10+ ) where
11+
12+import Prelude hiding (lookup)
13+
14+import Data.Proxy (Proxy (..))
15+import Data.Semigroup (Semigroup (..))
16+import GHC.Exts (fromList)
17+import GHC.TypeLits (Nat, SomeNat (..), someNatVal)
18+import Hedgehog (MonadGen, assert, forAll, (===))
19+import Test.Hspec (Arg, Expectation, Spec, SpecWith, describe, it)
20+import Test.Hspec.Hedgehog (hedgehog)
21+
22+import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), delete, insert, invariantCheck,
23+ lookup, member, union, generateOrderMapping, fromSortedList,
24+ adjust, alter, intersection)
25+
26+import qualified Hedgehog.Gen as Gen
27+import qualified Hedgehog.Range as Range
28+
29+
30+typeRepMapPropertySpec :: Spec
31+typeRepMapPropertySpec = describe "TypeRepMap Property tests" $ do
32+ describe "Map modification properties" $ do
33+ insertLookupSpec
34+ insertInsertSpec
35+ deleteMemberSpec
36+ insertInvariantSpec
37+ deleteInvariantSpec
38+ alterInsertSpec
39+ alterDeleteSpec
40+ alterAdjustSpec
41+ alterModifySpec
42+ intersectionSpec
43+ describe "Internal helpers" $ do
44+ generateOrderMappingInvariantSpec
45+ describe "Instance Laws" $ do
46+ semigroupAssocSpec
47+ monoidIdentitySpec
48+
49+----------------------------------------------------------------------------
50+-- Map modification properties
51+----------------------------------------------------------------------------
52+
53+type Property = SpecWith (Arg Expectation)
54+
55+insertLookupSpec :: Property
56+insertLookupSpec = it "lookup k (insert k v m) == Just v" $ hedgehog $ do
57+ m <- forAll genMap
58+ WrapTypeable (proxy :: IntProxy n) <- forAll genTF
59+ lookup @n @IntProxy (insert proxy m) === Just proxy
60+
61+insertInsertSpec :: Property
62+insertInsertSpec = it "insert k b . insert k a == insert k b" $ hedgehog $ do
63+ m <- forAll genMap
64+ WrapTypeable a@(IntProxy (proxy :: Proxy n) i) <- forAll genTF
65+ let b = IntProxy proxy (i + 1)
66+ lookup @n @IntProxy (insert b $ insert a m) === Just b
67+
68+deleteMemberSpec :: Property
69+deleteMemberSpec = it "member k . delete k == False" $ hedgehog $ do
70+ m <- forAll genMap
71+ WrapTypeable (proxy :: IntProxy n) <- forAll genTF
72+ shouldInsert <- forAll Gen.bool
73+
74+ if shouldInsert then
75+ member @n (delete @n $ insert proxy m) === False
76+ else
77+ member @n (delete @n m) === False
78+
79+insertInvariantSpec :: Property
80+insertInvariantSpec = it "invariantCheck (insert k b) == True" $ hedgehog $ do
81+ m <- forAll genMap
82+ WrapTypeable a <- forAll genTF
83+ assert $ invariantCheck (insert a m)
84+
85+deleteInvariantSpec :: Property
86+deleteInvariantSpec = it "invariantCheck (delete k b) == True" $ hedgehog $ do
87+ m <- forAll genMap
88+ WrapTypeable (_ :: IntProxy n) <- forAll genTF
89+ assert $ invariantCheck (delete @n m)
90+
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+
128+----------------------------------------------------------------------------
129+-- Internal helpers
130+----------------------------------------------------------------------------
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]
136+
137+----------------------------------------------------------------------------
138+-- Semigroup and Monoid laws
139+----------------------------------------------------------------------------
140+
141+semigroupAssocSpec :: Property
142+semigroupAssocSpec = it "x <> (y <> z) == (x <> y) <> z" $ hedgehog $ do
143+ x <- forAll genMap
144+ y <- forAll genMap
145+ z <- forAll genMap
146+ (x <> (y <> z)) === ((x <> y) <> z)
147+
148+monoidIdentitySpec :: Property
149+monoidIdentitySpec = it "x <> mempty == mempty <> x == x" $ hedgehog $ do
150+ x <- forAll genMap
151+ x <> mempty === x
152+ mempty <> x === x
153+
154+----------------------------------------------------------------------------
155+-- Generators
156+----------------------------------------------------------------------------
157+
158+data IntProxy (n :: Nat) = IntProxy (Proxy n) Int
159+ deriving stock (Show, Eq)
160+
161+genMap :: MonadGen m => m (TypeRepMap IntProxy)
162+genMap = fromList <$> Gen.list (Range.linear 0 1000) genTF
163+
164+genTF :: MonadGen m => m (WrapTypeable IntProxy)
165+genTF = do
166+ randNat :: Integer <- Gen.integral (Range.linear 0 10000)
167+ randInt <- Gen.int Range.constantBounded
168+ case someNatVal randNat of
169+ Just (SomeNat proxyNat) -> pure $ WrapTypeable $ IntProxy proxyNat randInt
170+ 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
test/Test/TypeRep/Vector.hs+8−6
@@ -1,16 +1,18 @@
1−module Test.TypeRep.Vector where
1+module Test.TypeRep.Vector
2+ ( vectorSpec
3+ ) where
24
35import Prelude hiding (lookup)
46
57import Data.Functor.Identity (Identity (..))
8+import Test.Hspec (Spec, describe, it, shouldBe)
69
7−import Test.Tasty.Hspec
10+import Data.TypeRep.Vector (TF (..), fromList, lookup)
811
9−import Data.TypeRep.Vector
1012
11−-- Simple test for 'lookup', 'insert' and 'size' functions.
12−spec_insertLookup :: Spec
13−spec_insertLookup =
13+-- | Simple test for 'lookup', 'insert' and 'size' functions.
14+vectorSpec :: Spec
15+vectorSpec = describe "Vector TypeRep" $
1416 describe "Lookup Test" $ do
1517 it "returns the inserted element" $
1618 lookup (fromList [TF (Identity 'a')]) `shouldBe` Just (Identity 'a')
test/Test/TypeRep/VectorOpt.hs+8−6
@@ -1,16 +1,18 @@
1−module Test.TypeRep.VectorOpt where
1+module Test.TypeRep.VectorOpt
2+ ( optimalVectorSpec
3+ ) where
24
35import Prelude hiding (lookup)
46
57import Data.Functor.Identity (Identity (..))
6−
7−import Test.Tasty.Hspec
8+import Test.Hspec (Spec, describe, it, shouldBe)
89
910import Data.TypeRep.OptimalVector (TF (..), fromList, lookup)
1011
11−-- Simple test for 'lookup', 'insert' and 'size' functions.
12−spec_insertLookup :: Spec
13−spec_insertLookup =
12+
13+-- | Simple test for 'lookup', 'insert' and 'size' functions.
14+optimalVectorSpec :: Spec
15+optimalVectorSpec = describe "Optimal Vector TypeRep" $
1416 describe "Lookup Test" $ do
1517 it "returns the inserted element" $
1618 lookup (fromList [TF $ Identity 'a']) `shouldBe` Just (Identity 'a')
typerep-extra-impls/Data/TypeRep/CMap.hs+16−3
@@ -1,5 +1,12 @@
1−{-# LANGUAGE KindSignatures #-}
2−{-# LANGUAGE PolyKinds #-}
1+{-# LANGUAGE PolyKinds #-}
2+
3+{- |
4+Copyright: (c) 2017-2020 Kowainik
5+SPDX-License-Identifier: MPL-2.0
6+Maintainer: Kowainik <xrom.xkov@gmail.com>
7+
8+TypeRepMap implementation based on @containers@ 'Map'.
9+-}
310
411module Data.TypeRep.CMap
512 ( TypeRepMap (..)
@@ -12,6 +19,8 @@ module Data.TypeRep.CMap
1219
1320import Prelude hiding (lookup)
1421
22+import Control.DeepSeq
23+import Data.Kind (Type)
1524import Data.Proxy (Proxy (..))
1625import Data.Typeable (TypeRep, Typeable, typeRep)
1726import GHC.Base (Any)
@@ -19,11 +28,15 @@ import Unsafe.Coerce (unsafeCoerce)
1928
2029import qualified Data.Map.Lazy as LMap
2130
31+
2232-- | Map-like data structure with types served as the keys.
23−newtype TypeRepMap (f :: k -> *) = TypeRepMap
33+newtype TypeRepMap (f :: k -> Type) = TypeRepMap
2434 { unMap :: LMap.Map TypeRep Any
2535 }
2636
37+instance NFData (TypeRepMap f) where
38+ rnf x = rnf (keys x) `seq` ()
39+
2740-- | Empty structure.
2841empty :: TypeRepMap f
2942empty = TypeRepMap mempty
typerep-extra-impls/Data/TypeRep/OptimalVector.hs+20−8
@@ -1,9 +1,16 @@
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 #-}
6+
7+{- |
8+Copyright: (c) 2017-2020 Kowainik
9+SPDX-License-Identifier: MPL-2.0
10+Maintainer: Kowainik <xrom.xkov@gmail.com>
11+
12+TypeRepMap implementation based on the optimal Vector.
13+-}
714
815module Data.TypeRep.OptimalVector
916 ( -- * Map type
@@ -23,6 +30,7 @@ module Data.TypeRep.OptimalVector
2330import Prelude hiding (lookup)
2431
2532import Control.Arrow ((&&&))
33+import Control.DeepSeq
2634import Data.Kind (Type)
2735import Data.Proxy (Proxy (..))
2836import Data.Typeable (Typeable, typeRep, typeRepFingerprint)
@@ -35,12 +43,16 @@ import Unsafe.Coerce (unsafeCoerce)
3543import qualified Data.Vector as V
3644import qualified Data.Vector.Unboxed as Unboxed
3745
46+
3847data TypeRepMap (f :: k -> Type) = TypeRepMap
3948 { fingerprintAs :: Unboxed.Vector Word64
4049 , fingerprintBs :: Unboxed.Vector Word64
4150 , anys :: V.Vector Any
4251 }
4352
53+instance NFData (TypeRepMap f) where
54+ rnf x = x `seq` ()
55+
4456fromAny :: Any -> f a
4557fromAny = unsafeCoerce
4658
@@ -49,7 +61,7 @@ empty :: TypeRepMap f
4961empty = TypeRepMap mempty mempty mempty
5062
5163-- | Inserts the value with its type as a key.
52−insert :: forall a f . Typeable a => a -> TypeRepMap f -> TypeRepMap f
64+insert :: forall a f . a -> TypeRepMap f -> TypeRepMap f
5365insert = undefined
5466
5567-- | Looks up the value at the type.
@@ -99,7 +111,7 @@ binarySearch (Fingerprint a b) fpAs fpBs =
99111data TF f where
100112 TF :: Typeable a => f a -> TF f
101113
102−fromF :: Typeable a => f a -> Proxy a
114+fromF :: f a -> Proxy a
103115fromF _ = Proxy
104116
105117fromList :: forall f . [TF f] -> TypeRepMap f
typerep-extra-impls/Data/TypeRep/Vector.hs+11−2
@@ -7,6 +7,14 @@
77{-# LANGUAGE PolyKinds #-}
88{-# LANGUAGE TypeFamilies #-}
99
10+{- |
11+Copyright: (c) 2017-2020 Kowainik
12+SPDX-License-Identifier: MPL-2.0
13+Maintainer: Kowainik <xrom.xkov@gmail.com>
14+
15+TypeRepMap implementation based on Vector.
16+-}
17+
1018module Data.TypeRep.Vector
1119 ( TypeRepVector (..)
1220 , TF (..)
@@ -33,6 +41,7 @@ import qualified Data.Vector.Generic as G
3341import qualified Data.Vector.Generic.Mutable as M
3442import qualified Data.Vector.Unboxed as Unboxed
3543
44+
3645data instance Unboxed.MVector s Fingerprint = MFingerprintVector (Unboxed.MVector s Word64) (Unboxed.MVector s Word64)
3746data instance Unboxed.Vector Fingerprint = FingerprintVector (Unboxed.Vector Word64) (Unboxed.Vector Word64)
3847
@@ -135,7 +144,7 @@ empty :: TypeRepVector f
135144empty = TypeRepVect mempty mempty
136145
137146-- | Inserts the value with its type as a key.
138−insert :: forall a f . Typeable a => a -> TypeRepVector f -> TypeRepVector f
147+insert :: forall a f . a -> TypeRepVector f -> TypeRepVector f
139148insert = undefined
140149
141150-- | Looks up the value at the type.
@@ -155,7 +164,7 @@ size = Unboxed.length . fingerprints
155164data TF f where
156165 TF :: Typeable a => f a -> TF f
157166
158−fromF :: Typeable a => f a -> Proxy a
167+fromF :: f a -> Proxy a
159168fromF _ = Proxy
160169
161170fromList :: forall f . [TF f] -> TypeRepVector f
typerep-map.cabal+70−56
@@ -1,5 +1,6 @@
1+cabal-version: 2.4
12name: typerep-map
2−version: 0.1.0
3+version: 0.3.3.0
34synopsis: Efficient implementation of a dependent map with types as keys
45description:
56 A dependent map from type representations to values of these types.
@@ -18,102 +19,115 @@ description:
1819
1920homepage: https://github.com/kowainik/typerep-map
2021bug-reports: https://github.com/kowainik/typerep-map/issues
21−license: MIT
22+license: MPL-2.0
2223license-file: LICENSE
23−author: Kowainik, Vladislav Zavialov
24−maintainer: xrom.xkov@gmail.com
25−copyright: 2017-2018 Kowainik
24+author: Veronika Romashkina, Vladislav Zavialov, Dmitrii Kovanikov
25+maintainer: Kowainik <xrom.xkov@gmail.com>
26+copyright: 2017-2020 Kowainik
2627category: Data, Data Structures, Types
2728build-type: Simple
2829extra-doc-files: README.md
2930 , CHANGELOG.md
30−cabal-version: 2.0
31−tested-with: GHC == 8.0.2
32− , GHC == 8.2.2
33− , GHC == 8.4.3
31+tested-with: GHC == 8.2.2
32+ , GHC == 8.4.4
33+ , GHC == 8.6.5
34+ , GHC == 8.8.3
35+ , GHC == 8.10.1
3436
3537source-repository head
3638 type: git
3739 location: https://github.com/kowainik/typerep-map.git
3840
41+common common-options
42+ build-depends: base >= 4.10 && < 4.15
43+
44+ default-language: Haskell2010
45+ default-extensions: BangPatterns
46+ DerivingStrategies
47+ OverloadedStrings
48+ RecordWildCards
49+ ScopedTypeVariables
50+ TypeApplications
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)
64+ ghc-options: -Wmissing-deriving-strategies
65+ -Werror=missing-deriving-strategies
66+ if impl(ghc >= 8.10)
67+ ghc-options: -Wunused-packages
68+
3969library
70+ import: common-options
4071 hs-source-dirs: src
4172 exposed-modules: Data.TMap
4273 Data.TypeRepMap
4374 Data.TypeRepMap.Internal
44− ghc-options: -Wall
45− build-depends: base >= 4.9 && < 5
46− , containers
47− , ghc-prim
48− , primitive >= 0.6.4
49− default-extensions: OverloadedStrings
50− RecordWildCards
51− ScopedTypeVariables
52− TypeApplications
53− default-language: Haskell2010
75+
76+ build-depends: ghc-prim >= 0.5.1.1 && < 0.7
77+ , primitive ^>= 0.7.0
78+ , deepseq ^>= 1.4
5479
5580library typerep-extra-impls
81+ import: common-options
5682 hs-source-dirs: typerep-extra-impls
5783 exposed-modules: Data.TypeRep.CMap
5884 Data.TypeRep.OptimalVector
5985 Data.TypeRep.Vector
60− ghc-options: -Wall
61− build-depends: base
62− , containers
63− , vector
64− default-extensions: OverloadedStrings
65− RecordWildCards
66− ScopedTypeVariables
67− TypeApplications
6886
69− default-language: Haskell2010
87+ build-depends: containers >= 0.5.10.2 && < 0.7
88+ , vector ^>= 0.12.0.1
89+ , deepseq ^>= 1.4
7090
7191test-suite typerep-map-test
92+ import: common-options
7293 type: exitcode-stdio-1.0
7394 hs-source-dirs: test
95+
7496 main-is: Test.hs
7597 other-modules: Test.TypeRep.CMap
76− , Test.TypeRep.CacheMap
77− , Test.TypeRep.MapProperty
98+ , Test.TypeRep.TypeRepMap
99+ , Test.TypeRep.TypeRepMapProperty
78100 , Test.TypeRep.Vector
79101 , Test.TypeRep.VectorOpt
80− build-depends: base
81− , ghc-typelits-knownnat
82− , hedgehog
102+
103+ build-depends: ghc-typelits-knownnat >= 0.4.2 && < 0.8
104+ , hedgehog ^>= 1.0
105+ , hspec ^>= 2.7.1
106+ , hspec-hedgehog ^>= 0.0.1
83107 , typerep-map
84108 , typerep-extra-impls
85− , tasty
86− , tasty-discover >= 4.1.1
87− , tasty-hedgehog
88− , tasty-hspec
89− build-tool-depends: tasty-discover:tasty-discover
90− ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
91− default-extensions: ScopedTypeVariables
92− TypeApplications
93109
94− default-language: Haskell2010
110+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
95111
96112benchmark typerep-map-benchmark
113+ import: common-options
97114 type: exitcode-stdio-1.0
98− default-language: Haskell2010
99− ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N -freduction-depth=0
100115 hs-source-dirs: benchmark
116+
101117 main-is: Main.hs
102118 other-modules: CMap
103119 , CacheMap
120+ , DMap
121+ , Spec
104122 , Vector
105123 , OptimalVector
106− build-depends: base
107− , criterion
108− , deepseq
109− , dependent-map
110− , dependent-sum
111− , ghc-typelits-knownnat
124+
125+ build-depends: criterion >= 1.4.1.0 && < 1.6
126+ , deepseq ^>= 1.4.3.0
127+ , dependent-map >= 0.2.4.0 && < 0.5
128+ , dependent-sum >= 0.5 && < 0.8
129+ , ghc-typelits-knownnat >= 0.4.2 && < 0.8
112130 , typerep-map
113131 , typerep-extra-impls
114− default-extensions: OverloadedStrings
115− RecordWildCards
116− ScopedTypeVariables
117− TypeApplications
118− if impl(ghc >= 8.2.2)
119− other-modules: DMap
132+
133+ ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N -freduction-depth=0