on main help sign in
34 files changed+3012−197
since splitsnapshots
unifiedsplit
.github/CODEOWNERS+1 .github/workflows/ci.yml+47 .gitignore+3 .hlint.yaml+6 .stylish-haskell.yaml+59 .travis.yml+23−23 CHANGELOG.md+53−7 LICENSE+373−21 README.md+64−3 Setup.hs−3 appveyor.yml+35 benchmark/CMap.hs+77 benchmark/CacheMap.hs+79 benchmark/DMap.hs+89 benchmark/Main.hs+43−22 benchmark/OptimalVector.hs+60 benchmark/Spec.hs+64 benchmark/Vector.hs+63 src/Data/TMap.hs+220 src/Data/TypeRep/Map.hs−44 src/Data/TypeRepMap.hs+81 src/Data/TypeRepMap/Internal.hs+670 stack.yaml+4−9 test/Test.hs+21−1 test/Test/TypeRep/CMap.hs+43 test/Test/TypeRep/Map.hs−33 test/Test/TypeRep/TypeRepMap.hs+111 test/Test/TypeRep/TypeRepMapProperty.hs+184 test/Test/TypeRep/Vector.hs+20 test/Test/TypeRep/VectorOpt.hs+20 typerep-extra-impls/Data/TypeRep/CMap.hs+61 typerep-extra-impls/Data/TypeRep/OptimalVector.hs+127 typerep-extra-impls/Data/TypeRep/Vector.hs+197 typerep-map.cabal+114−31
.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+3
@@ -20,8 +20,11 @@ cabal.sandbox.config
2020cabal.config
2121cabal.project.local
2222.HTF/
23+.ghc.environment.*
24+
2325# Stack
2426.stack-work/
27+stack.yaml.lock
2528
2629### IDE/support
2730# Vim
.hlint.yamlnew file+6
@@ -0,0 +1,6 @@
1+- ignore: {name: "Use camelCase"}
2+
3+- arguments:
4+ - -XTypeApplications
5+
6+- warn: {name: "Use explicit module export list"}
.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+23−23
@@ -1,28 +1,28 @@
1−# Use new container infrastructure to enable caching
2−sudo: false
1+sudo: true
2+language: generic
33
4−# Choose a lightweight base image; we provide our own build tools.
5−language: c
4+git:
5+ depth: 5
66
7−# GHC depends on GMP. You can add other dependencies here as well.
8−addons:
9− apt:
10− packages:
11− - libgmp-dev
7+cache:
8+ directories:
9+ - "$HOME/.stack"
1210
13−before_install:
14−# Download and unpack the stack executable
15−- mkdir -p ~/.local/bin
16−- export PATH=$HOME/.local/bin:$PATH
17−- travis_retry curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'
11+matrix:
12+ include:
13+ - env: GHC='8.8.3'
14+ os: linux
15+ - env: GHC='8.8.3'
16+ os: osx
1817
19−# This line does all of the work: installs GHC if necessary, builds the
20−# library, executables, and test suites, and runs the test suites.
21−# `--no-terminal works` around some quirks in Travis's terminal implementation.
22−script: stack --no-terminal --install-ghc test
18+install:
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
2323
24−# Caching so the next build will be fast too.
25−cache:
26− directories:
27− - $HOME/.stack
28− - $HOME/build/vrom911/typerep-map/.stack-work
24+script:
25+ - stack test --no-terminal
26+
27+notifications:
28+ email: false
CHANGELOG.md+53−7
@@ -1,10 +1,56 @@
1−Change log
2−==========
1+# Changelog
32
4−typerep-map uses [PVP Versioning][1].
5−The change log is available [on GitHub][2].
3+`typerep-map` uses [PVP Versioning][1].
4+The changelog is available [on GitHub][2].
5+
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
652
7−[1]: https://pvp.haskell.org
8−[2]: https://github.com/vrom911/typerep-map/releases
9−# 0.1.0.0
1053* Initially created.
54+
55+[1]: https://pvp.haskell.org
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+64−3
@@ -1,5 +1,66 @@
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/vrom911/typerep-map.svg)](https://travis-ci.org/vrom911/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+
13+
14+`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.
15+
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+
20+## Usage example
21+
22+```haskell
23+ghci> import Data.TMap
24+
25+ghci> tm = insert True $ one (42 :: Int)
26+
27+ghci> size tm
28+2
29+
30+ghci> res = lookup tm
31+
32+ghci> res :: Maybe Int
33+Just 42
34+
35+ghci> res :: Maybe Bool
36+Just True
37+
38+ghci> res :: Maybe String
39+Nothing
40+
41+ghci> lookup (insert "hello" tm) :: Maybe String
42+Just "hello"
43+
44+ghci> member @Int tm
45+True
46+
47+ghci> tm' = delete @Int tm
48+
49+ghci> member @Int tm'
50+False
51+```
52+
53+## Benchmarks
54+
55+Tables below contain comparision with `DMap TypeRep` of ten `lookup` operations
56+on structure with size `10^4`:
57+
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 |
62+
63+ ghc-8.2.2 | ghc-8.4.3
64+:---------:|:-----------:
65+![DMap 8.2.2](https://user-images.githubusercontent.com/4276606/42495129-c700f21e-8454-11e8-98b4-ba080259c712.png) | ![DMap 8.4.3](https://user-images.githubusercontent.com/4276606/42495168-ebb1d13c-8454-11e8-9d17-f6da29d2169a.png)
66+![TMap 8.2.2](https://user-images.githubusercontent.com/4276606/42494935-3a352d96-8454-11e8-985e-ebc77cc51ca0.png) | ![TMap 8.4.3](https://user-images.githubusercontent.com/4276606/42495147-d884bdf4-8454-11e8-887f-9815fd2b8d68.png)
Setup.hsdeleted−3
@@ -1,3 +0,0 @@
1−import Distribution.Simple
2−
3−main = defaultMain
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.hsnew file+77
@@ -0,0 +1,77 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE PolyKinds #-}
5+{-# LANGUAGE TypeFamilies #-}
6+{-# LANGUAGE TypeOperators #-}
7+{-# LANGUAGE UndecidableInstances #-}
8+
9+module CMap
10+ ( spec
11+ ) where
12+
13+import Prelude hiding (lookup)
14+
15+import Criterion.Main (bench, env, nf, whnf)
16+import Data.Kind (Type)
17+import Data.Maybe (fromJust)
18+import Data.Proxy (Proxy (..))
19+import Data.Typeable (Typeable)
20+import GHC.TypeLits (type (+), KnownNat, Nat)
21+
22+import Data.TypeRep.CMap (TypeRepMap (..), empty, insert, lookup)
23+
24+import Spec (BenchSpec (..))
25+
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+ )
50+tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
51+ where
52+ lp :: forall (a::Nat). Typeable a => Proxy a
53+ lp = fromJust $ lookup tmap
54+
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
69+
70+buildBigMap
71+ :: forall a . (KnownNat a)
72+ => Int
73+ -> Proxy (a :: Nat)
74+ -> TypeRepMap (Proxy :: Nat -> Type)
75+ -> TypeRepMap (Proxy :: Nat -> Type)
76+buildBigMap 1 x = insert x
77+buildBigMap n x = insert x . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
benchmark/CacheMap.hsnew file+79
@@ -0,0 +1,79 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE PolyKinds #-}
5+{-# LANGUAGE TypeFamilies #-}
6+{-# LANGUAGE TypeOperators #-}
7+{-# LANGUAGE UndecidableInstances #-}
8+
9+module CacheMap
10+ ( spec
11+ ) where
12+
13+import Prelude hiding (lookup)
14+
15+import Criterion.Main (bench, env, nf, whnf)
16+import Data.Kind (Type)
17+import Data.Maybe (fromJust)
18+import Data.Proxy (Proxy (..))
19+import Data.Typeable (Typeable)
20+import GHC.Exts (fromList)
21+import GHC.TypeLits (type (+), KnownNat, Nat)
22+
23+import Data.TypeRepMap.Internal (TypeRepMap (..), WrapTypeable (..), empty, insert, lookup)
24+
25+import Spec (BenchSpec (..))
26+
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+ )
51+tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
52+ where
53+ lp :: forall (a :: Nat) . Typeable a => Proxy a
54+ lp = fromJust $ lookup tmap
55+
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.hsnew file+89
@@ -0,0 +1,89 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE InstanceSigs #-}
5+{-# LANGUAGE PolyKinds #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeOperators #-}
8+{-# LANGUAGE UndecidableInstances #-}
9+
10+module DMap
11+ ( spec
12+ ) where
13+
14+import Prelude hiding (lookup)
15+
16+import Control.DeepSeq (NFData (..))
17+import Criterion.Main (bench, env, nf, whnf)
18+import Data.Kind (Type)
19+import Data.Maybe (fromJust)
20+import Data.Proxy (Proxy (..))
21+import GHC.TypeLits (type (+), KnownNat, Nat)
22+import Type.Reflection (TypeRep, Typeable, typeRep)
23+import Type.Reflection.Unsafe (typeRepFingerprint)
24+
25+import Data.Dependent.Map (DMap, empty, insert, keys, lookup)
26+import Data.Some (Some (Some))
27+
28+import Spec (BenchSpec (..))
29+
30+
31+type TypeRepMap = DMap TypeRep
32+
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+ }
46+
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+ )
52+tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
53+ where
54+ lp :: forall (a :: Nat) . Typeable a => Proxy a
55+ lp = fromJust $ lookup (typeRep @a) tmap
56+
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+
69+-- TypeRepMap of 10000 elements
70+mkBigMap :: IO (DMapNF (Proxy :: Nat -> Type))
71+mkBigMap = pure . DMapNF $ buildBigMap 10000 (Proxy :: Proxy 0) empty
72+
73+buildBigMap
74+ :: forall a . (KnownNat a)
75+ => Int
76+ -> Proxy (a :: Nat)
77+ -> TypeRepMap (Proxy :: Nat -> Type)
78+ -> TypeRepMap (Proxy :: Nat -> Type)
79+buildBigMap 1 x = insert (typeRep @a) x
80+buildBigMap n x = insert (typeRep @a) x
81+ . buildBigMap (n - 1) (Proxy @(a + 1))
82+
83+-- | Wrapper that provides NFData instance to the 'DMap' structure.
84+newtype DMapNF f = DMapNF (TypeRepMap f)
85+
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+43−22
@@ -1,31 +1,52 @@
1−{-# LANGUAGE ExplicitForAll #-}
2−{-# LANGUAGE ScopedTypeVariables #-}
1+{-# LANGUAGE CPP #-}
32
4−import Criterion.Main (bench, defaultMain, whnf)
3+module Main
4+ ( main
5+ ) where
56
6−import Prelude hiding (lookup)
7+import Criterion.Main (bgroup, defaultMain)
78
8−import Data.Proxy (Proxy (..))
9−import Data.Typeable (Typeable)
9+import Spec (BenchSpec (..))
10+
11+import qualified CacheMap
12+import qualified CMap
13+#if ( __GLASGOW_HASKELL__ >= 802 )
14+import qualified DMap
15+#endif
16+import qualified OptimalVector as OptVec
1017
11−import Data.TypeRep.Map (TypeRepMap, empty, insert, lookup, size)
1218
1319main :: IO ()
1420main = do
15− putStrLn $ "size: " ++ (show $ size bigMap)
21+ let specs = [("CMap", CMap.spec)
22+ ,("CacheMap", CacheMap.spec)
23+#if ( __GLASGOW_HASKELL__ >= 802 )
24+ , ("DMap", DMap.spec)
25+#endif
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+ ]
1642 defaultMain
17− [ bench "lookup" $ whnf (lookup :: TypeRepMap -> Maybe Char) bigMap
18− , bench "insert new" $ whnf (\x -> insert x bigMap) (111 :: Int)
19− , bench "update old" $ whnf (\x -> insert x bigMap) 'b'
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+ ]
2052 ]
21−
22−-- TypeRepMap of 10000 elements
23−bigMap :: TypeRepMap
24−bigMap = buildBigMap 10000 (Proxy :: Proxy Z) empty
25−
26−data Z
27−data S a
28−
29−buildBigMap :: forall a . Typeable a => Int -> Proxy a -> TypeRepMap -> TypeRepMap
30−buildBigMap 1 x = insert 'a' . insert x
31−buildBigMap n x = insert x . buildBigMap (n - 1) (Proxy :: Proxy (S a))
benchmark/OptimalVector.hsnew file+60
@@ -0,0 +1,60 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE PolyKinds #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeOperators #-}
8+{-# LANGUAGE UndecidableInstances #-}
9+
10+module OptimalVector
11+ ( spec
12+ ) where
13+
14+import Prelude hiding (lookup)
15+
16+import Criterion.Main (bench, env, nf)
17+import Data.Kind (Type)
18+import Data.Maybe (fromJust)
19+import Data.Proxy (Proxy (..))
20+import Data.Typeable (Typeable)
21+import GHC.TypeLits (type (+), KnownNat, Nat)
22+
23+import Data.TypeRep.OptimalVector (TF (..), TypeRepMap (..), fromList, lookup)
24+
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+ )
44+tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
45+ where
46+ lp :: forall (a::Nat). Typeable a => Proxy a
47+ lp = fromJust $ lookup tmap
48+
49+-- TypeRepMap of 10000 elements
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)]
59+buildBigMap 1 x = (TF x :)
60+buildBigMap n x = (TF x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
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.hsnew file+63
@@ -0,0 +1,63 @@
1+{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
2+
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE PolyKinds #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE TypeFamilies #-}
7+{-# LANGUAGE TypeOperators #-}
8+{-# LANGUAGE UndecidableInstances #-}
9+
10+module Vector
11+ ( benchVector
12+ , prepareBenchVector
13+ ) where
14+
15+import Prelude hiding (lookup)
16+
17+import Control.DeepSeq (rnf)
18+import Control.Exception (evaluate)
19+import Criterion.Main (Benchmark, bench, bgroup, nf)
20+import Data.Kind (Type)
21+import Data.Maybe (fromJust)
22+import Data.Proxy (Proxy (..))
23+import Data.Typeable (Typeable)
24+import GHC.TypeLits (type (+), KnownNat, Nat)
25+
26+import Data.TypeRep.Vector (TF (..), TypeRepVector, fingerprints, fromList, lookup)
27+
28+
29+benchVector :: Benchmark
30+benchVector = bgroup "vector"
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+ ]
35+
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+ )
41+tenLookups tmap = (lp, lp, lp, lp, lp, lp, lp, lp)
42+ where
43+ lp :: forall (a::Nat). Typeable a => Proxy a
44+ lp = fromJust $ lookup tmap
45+
46+-- TypeRepMap of 10000 elements
47+bigMap :: TypeRepVector (Proxy :: Nat -> Type)
48+bigMap = fromList $ buildBigMap 10000 (Proxy :: Proxy 0) []
49+
50+buildBigMap
51+ :: forall a . (KnownNat a)
52+ => Int
53+ -> Proxy (a :: Nat)
54+ -> [TF (Proxy :: Nat -> Type)]
55+ -> [TF (Proxy :: Nat -> Type)]
56+buildBigMap 1 x = (TF x :)
57+buildBigMap n x = (TF x :) . buildBigMap (n - 1) (Proxy :: Proxy (a + 1))
58+
59+rknf :: TypeRepVector f -> ()
60+rknf = rnf . fingerprints
61+
62+prepareBenchVector :: IO ()
63+prepareBenchVector = evaluate (rknf bigMap)
src/Data/TMap.hsnew file+220
@@ -0,0 +1,220 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE Rank2Types #-}
3+
4+{- |
5+Copyright: (c) 2017-2020 Kowainik
6+SPDX-License-Identifier: MPL-2.0
7+Maintainer: Kowainik <xrom.xkov@gmail.com>
8+
9+'TMap' is a heterogeneous data structure similar in its essence to
10+'Data.Map.Map' with types as keys, where each value has the type of its key.
11+
12+Here is an example of a 'TMap' with a comparison to 'Data.Map.Map':
13+
14+@
15+ 'Data.Map.Map' 'Prelude.String' 'Prelude.String' 'TMap'
16+-------------------- -----------------
17+ \"Int\" -> \"5\" 'Prelude.Int' -> 5
18+ \"Bool\" -> \"True\" 'Prelude.Bool' -> 'Prelude.True'
19+ \"Char\" -> \"\'x\'\" 'Prelude.Char' -> \'x\'
20+@
21+
22+The runtime representation of 'TMap' is an array, not a tree. This makes
23+'lookup' significantly more efficient.
24+
25+-}
26+
27+module Data.TMap
28+ ( -- * Map type
29+ TMap
30+
31+ -- * Construction
32+ , empty
33+ , one
34+
35+ -- * Modification
36+ , insert
37+ , delete
38+ , unionWith
39+ , union
40+ , intersectionWith
41+ , intersection
42+ , map
43+ , adjust
44+ , alter
45+
46+ -- * Query
47+ , lookup
48+ , member
49+ , size
50+ , keys
51+ , keysWith
52+ , toListWith
53+ ) where
54+
55+import Prelude hiding (lookup, map)
56+
57+import Data.Functor.Identity (Identity (..))
58+import Data.Typeable (Typeable)
59+import GHC.Exts (coerce)
60+import Type.Reflection (SomeTypeRep, TypeRep)
61+
62+import qualified Data.TypeRepMap as F
63+
64+-- | 'TMap' is a special case of 'F.TypeRepMap' when the interpretation is
65+-- 'Identity'.
66+type TMap = F.TypeRepMap Identity
67+
68+{- |
69+
70+A 'TMap' with no values stored in it.
71+
72+prop> size empty == 0
73+prop> member @a empty == False
74+
75+-}
76+empty :: TMap
77+empty = F.empty
78+{-# INLINE empty #-}
79+
80+{- |
81+
82+Construct a 'TMap' with a single element.
83+
84+prop> size (one x) == 1
85+prop> member @a (one (x :: a)) == True
86+
87+-}
88+one :: forall a . Typeable a => a -> TMap
89+one x = coerce (F.one @a @Identity $ coerce x)
90+{-# INLINE one #-}
91+
92+{- |
93+
94+Insert a value into a 'TMap'.
95+TMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
96+
97+prop> size (insert v tm) >= size tm
98+prop> member @a (insert (x :: a) tm) == True
99+
100+-}
101+insert :: forall a . Typeable a => a -> TMap -> TMap
102+insert x = coerce (F.insert @a @Identity $ coerce x)
103+{-# INLINE insert #-}
104+
105+{- | Delete a value from a 'TMap'.
106+
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+
110+prop> size (delete @a tm) <= size tm
111+prop> member @a (delete @a tm) == False
112+
113+>>> tm = delete @Bool $ insert True $ one 'a'
114+>>> size tm
115+1
116+>>> member @Bool tm
117+False
118+>>> member @Char tm
119+True
120+-}
121+delete :: forall a . Typeable a => TMap -> TMap
122+delete = F.delete @a @Identity
123+{-# INLINE delete #-}
124+
125+-- | The union of two 'TMap's using a combining function.
126+unionWith :: (forall x. Typeable x => x -> x -> x) -> TMap -> TMap -> TMap
127+unionWith f = F.unionWith fId
128+ where
129+ fId :: forall y . Typeable y => Identity y -> Identity y -> Identity y
130+ fId y1 y2 = Identity $ f (coerce y1) (coerce y2)
131+{-# INLINE unionWith #-}
132+
133+-- | The (left-biased) union of two 'TMap's. It prefers the first map when
134+-- duplicate keys are encountered, i.e. @'union' == 'unionWith' const@.
135+union :: TMap -> TMap -> TMap
136+union = F.union
137+{-# INLINE union #-}
138+
139+-- | The intersection of two 'TMap's using a combining function.
140+--
141+-- @O(n + m)@
142+intersectionWith :: (forall x. Typeable x => x -> x -> x) -> TMap -> TMap -> TMap
143+intersectionWith f = F.intersectionWith fId
144+ where
145+ fId :: forall y . Typeable y => Identity y -> Identity y -> Identity y
146+ fId y1 y2 = f (coerce y1) (coerce y2)
147+{-# INLINE intersectionWith #-}
148+
149+-- | The intersection of two 'TMap's.
150+-- It keeps all values from the first map whose keys are present in the second.
151+--
152+-- @O(n + m)@
153+intersection :: TMap -> TMap -> TMap
154+intersection = F.intersection
155+{-# INLINE intersection #-}
156+
157+{- | Lookup a value of the given type in a 'TMap'.
158+
159+>>> x = lookup $ insert (11 :: Int) empty
160+>>> x :: Maybe Int
161+Just 11
162+>>> x :: Maybe ()
163+Nothing
164+-}
165+lookup :: forall a. Typeable a => TMap -> Maybe a
166+lookup = coerce (F.lookup @a @Identity)
167+{-# INLINE lookup #-}
168+
169+{- | Check if a value of the given type is present in a 'TMap'.
170+
171+>>> member @Char $ one 'a'
172+True
173+>>> member @Bool $ one 'a'
174+False
175+-}
176+member :: forall a . Typeable a => TMap -> Bool
177+member = F.member @a @Identity
178+{-# INLINE member #-}
179+
180+-- | Get the amount of elements in a 'TMap'.
181+size :: TMap -> Int
182+size = F.size
183+{-# 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/TypeRep/Map.hsdeleted−44
@@ -1,44 +0,0 @@
1−{-# LANGUAGE ExplicitForAll #-}
2−{-# LANGUAGE ScopedTypeVariables #-}
3−
4−module Data.TypeRep.Map
5− ( TypeRepMap (..)
6− , empty
7− , insert
8− , lookup
9− , size
10− ) where
11−
12−import Prelude hiding (lookup)
13−
14−import Data.Proxy (Proxy (..))
15−import Data.Typeable (TypeRep, Typeable, typeOf, typeRep)
16−import GHC.Base (Any)
17−import Unsafe.Coerce (unsafeCoerce)
18−
19−import qualified Data.Map.Lazy as LMap
20−
21−-- | Map-like data structure with types served as the keys.
22−newtype TypeRepMap = TypeRepMap
23− { unMap :: LMap.Map TypeRep Any
24− }
25−
26−-- | Empty structure.
27−empty :: TypeRepMap
28−empty = TypeRepMap mempty
29−
30−-- | Inserts the value with its type as a key.
31−insert :: Typeable a => a -> TypeRepMap -> TypeRepMap
32−insert val = TypeRepMap . LMap.insert (typeOf val) (unsafeCoerce val) . unMap
33−
34−-- | Looks up the value at the type.
35−-- >>> let x = lookup $ insert (11 :: Int) empty
36−-- >>> x :: Maybe Int
37−-- Just 11
38−-- >>> x :: Maybe ()
39−-- Nothing
40−lookup :: forall a . Typeable a => TypeRepMap -> Maybe a
41−lookup = fmap unsafeCoerce . LMap.lookup (typeRep (Proxy :: Proxy a)) . unMap
42−
43−size :: TypeRepMap -> Int
44−size = LMap.size . unMap
src/Data/TypeRepMap.hsnew file+81
@@ -0,0 +1,81 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
2+
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+--}
48+module Data.TypeRepMap
49+ ( -- * Map type
50+ TypeRepMap()
51+
52+ -- * Construction
53+ , empty
54+ , one
55+
56+ -- * Modification
57+ , insert
58+ , delete
59+ , adjust
60+ , alter
61+ , hoist
62+ , hoistA
63+ , hoistWithKey
64+ , unionWith
65+ , union
66+ , intersectionWith
67+ , intersection
68+
69+ -- * Query
70+ , lookup
71+ , member
72+ , size
73+ , keys
74+ , keysWith
75+ , toListWith
76+
77+ -- * 'IsList'
78+ , WrapTypeable (..)
79+ ) where
80+
81+import Data.TypeRepMap.Internal
src/Data/TypeRepMap/Internal.hsnew file+670
@@ -0,0 +1,670 @@
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
19+
20+-- {-# OPTIONS_GHC -ddump-simpl -dsuppress-idinfo -dsuppress-coercions -dsuppress-type-applications -dsuppress-uniques -dsuppress-module-prefixes #-}
21+
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+
36+module Data.TypeRepMap.Internal where
37+
38+import Prelude hiding (lookup)
39+
40+import Control.DeepSeq
41+import Control.Monad.ST (ST, runST)
42+import Control.Monad.Zip (mzip)
43+import Data.Function (on)
44+import Data.Kind (Type)
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(..))
52+import GHC.Base (Any, Int (..), Int#, (*#), (+#), (<#))
53+import GHC.Exts (IsList (..), inline, sortWith)
54+import GHC.Fingerprint (Fingerprint (..))
55+#if WORD_SIZE_IN_BITS >= 64
56+import GHC.Prim (eqWord#, ltWord#)
57+#else
58+import GHC.IntWord64 (eqWord64#, ltWord64#)
59+#define eqWord eqWord64
60+#define ltWord ltWord64
61+#endif
62+import GHC.Word (Word64 (..))
63+import Type.Reflection (SomeTypeRep (..), TypeRep, Typeable, typeRep, withTypeable)
64+import Type.Reflection.Unsafe (typeRepFingerprint)
65+import Unsafe.Coerce (unsafeCoerce)
66+
67+import qualified GHC.Exts as GHC (fromList, toList)
68+
69+{- |
70+
71+'TypeRepMap' is a heterogeneous data structure similar in its essence to
72+'Data.Map.Map' with types as keys, where each value has the type of its key. In
73+addition to that, each value is wrapped in an interpretation @f@.
74+
75+Here is an example of using 'Prelude.Maybe' as an interpretation, with a
76+comparison to 'Data.Map.Map':
77+
78+@
79+ 'Data.Map.Map' 'Prelude.String' ('Prelude.Maybe' 'Prelude.String') 'TypeRepMap' 'Prelude.Maybe'
80+--------------------------- ---------------------
81+ \"Int\" -> Just \"5\" 'Prelude.Int' -> Just 5
82+ \"Bool\" -> Just \"True\" 'Prelude.Bool' -> Just 'Prelude.True'
83+ \"Char\" -> Nothing 'Prelude.Char' -> Nothing
84+@
85+
86+The runtime representation of 'TypeRepMap' is an array, not a tree. This makes
87+'lookup' significantly more efficient.
88+
89+-}
90+data TypeRepMap (f :: k -> Type) =
91+ TypeRepMap
92+ { fingerprintAs :: {-# UNPACK #-} !(PrimArray Word64) -- ^ first components of key fingerprints
93+ , fingerprintBs :: {-# UNPACK #-} !(PrimArray Word64) -- ^ second components of key fingerprints
94+ , trAnys :: {-# UNPACK #-} !(Array Any) -- ^ values stored in the map
95+ , trKeys :: {-# UNPACK #-} !(Array Any) -- ^ typerep keys
96+ }
97+ -- ^ an unsafe constructor for 'TypeRepMap'
98+
99+instance NFData (TypeRepMap f) where
100+ rnf x = rnf (keys x) `seq` ()
101+
102+-- | Shows only keys.
103+instance Show (TypeRepMap f) where
104+ show TypeRepMap{..} = "TypeRepMap [" ++ showKeys ++ "]"
105+ where
106+ showKeys :: String
107+ showKeys = intercalate ", " $ toList $ mapArray' (show . anyToTypeRep) trKeys
108+
109+-- | Uses 'union' to combine 'TypeRepMap's.
110+instance Semigroup (TypeRepMap f) where
111+ (<>) :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
112+ (<>) = union
113+ {-# INLINE (<>) #-}
114+
115+instance Monoid (TypeRepMap f) where
116+ mempty = TypeRepMap mempty mempty mempty mempty
117+ mappend = (<>)
118+ {-# INLINE mempty #-}
119+ {-# INLINE mappend #-}
120+
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+
146+-- | Returns the list of 'Fingerprint's from 'TypeRepMap'.
147+toFingerprints :: TypeRepMap f -> [Fingerprint]
148+toFingerprints TypeRepMap{..} =
149+ zipWith Fingerprint (GHC.toList fingerprintAs) (GHC.toList fingerprintBs)
150+
151+{- |
152+
153+A 'TypeRepMap' with no values stored in it.
154+
155+prop> size empty == 0
156+prop> member @a empty == False
157+
158+-}
159+empty :: TypeRepMap f
160+empty = mempty
161+{-# INLINE empty #-}
162+
163+{- |
164+
165+Construct a 'TypeRepMap' with a single element.
166+
167+prop> size (one x) == 1
168+prop> member @a (one (x :: f a)) == True
169+
170+-}
171+one :: forall a f . Typeable a => f a -> TypeRepMap f
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)
178+{-# INLINE one #-}
179+
180+{- |
181+
182+Insert a value into a 'TypeRepMap'.
183+TypeRepMap optimizes for fast reads rather than inserts, as a trade-off inserts are @O(n)@.
184+
185+prop> size (insert v tm) >= size tm
186+prop> member @a (insert (x :: f a) tm) == True
187+
188+-}
189+insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
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)}
195+ where
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
202+{-# INLINE insert #-}
203+
204+-- Extract the kind of a type. We use it to work around lack of syntax for
205+-- inferred type variables (which are not subject to type applications).
206+type KindOf (a :: k) = k
207+
208+type ArgKindOf (f :: k -> l) = k
209+
210+{- | Delete a value from a 'TypeRepMap'.
211+
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+
215+prop> size (delete @a tm) <= size tm
216+prop> member @a (delete @a tm) == False
217+
218+>>> tm = delete @Bool $ insert (Just True) $ one (Just 'a')
219+>>> size tm
220+1
221+>>> member @Bool tm
222+False
223+>>> member @Char tm
224+True
225+-}
226+delete :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> TypeRepMap f
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
234+{-# INLINE delete #-}
235+
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+
282+{- | Map over the elements of a 'TypeRepMap'.
283+
284+>>> tm = insert (Identity True) $ one (Identity 'a')
285+>>> lookup @Bool tm
286+Just (Identity True)
287+>>> lookup @Char tm
288+Just (Identity 'a')
289+>>> tm2 = hoist ((:[]) . runIdentity) tm
290+>>> lookup @Bool tm2
291+Just [True]
292+>>> lookup @Char tm2
293+Just "a"
294+-}
295+hoist :: (forall x. f x -> g x) -> TypeRepMap f -> TypeRepMap g
296+hoist f (TypeRepMap as bs ans ks) = TypeRepMap as bs (mapArray' (toAny . f . fromAny) ans) ks
297+{-# INLINE hoist #-}
298+
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)
318+ where
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
347+{-# INLINE unionWith #-}
348+
349+-- | The (left-biased) union of two 'TypeRepMap's in @O(n + m)@. It prefers the first map when
350+-- duplicate keys are encountered, i.e. @'union' == 'unionWith' const@.
351+union :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
352+union = unionWith const
353+{-# INLINE union #-}
354+
355+-- | The 'intersection' of two 'TypeRepMap's using a combining function
356+--
357+-- @O(n + m)@
358+intersectionWith :: forall f. (forall x. Typeable x => f x -> f x -> f x) -> TypeRepMap f -> TypeRepMap f -> TypeRepMap f
359+intersectionWith f ma mb =
360+ fromSortedTriples $ mergeMaps (toSortedTriples ma) (toSortedTriples mb)
361+ where
362+ f' :: forall x. TypeRep x -> f x -> f x -> f x
363+ f' tr = withTypeable tr f
364+
365+ combine :: (Fingerprint, Any, Any) -> (Fingerprint, Any, Any) -> (Fingerprint, Any, Any)
366+ combine (fp, av, ak) (_, bv, _) = (fp, toAny $ f' (fromAny ak) (fromAny av) (fromAny bv), ak)
367+
368+ -- Merges two typrepmaps into a sorted, dedup'd list of triples.
369+ mergeMaps :: [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)] -> [(Fingerprint, Any, Any)]
370+ -- If either list is empty, the intersection must be finished.
371+ mergeMaps _ [] = []
372+ mergeMaps [] _ = []
373+ -- Merge the two maps considering one element at a time.
374+ mergeMaps (a@(af, _, _) : as) (b@(bf, _, _) : bs) =
375+ case compare af bf of
376+ -- Fingerprints are equal, union the elements using our function
377+ -- If the incoming maps were de-duped, there shouldn't be any other equivalent
378+ -- fingerprints
379+ EQ -> combine a b : mergeMaps as bs
380+ -- First fingerprint must not be in the second map or we would have seen it by now
381+ -- Skip it an move on
382+ LT -> mergeMaps as (b : bs)
383+ -- Second fingerprint must not be in the first map or we would have seen it by now
384+ -- Skip it an move on
385+ GT -> mergeMaps (a:as) bs
386+{-# INLINE intersectionWith #-}
387+
388+-- | The intersection of two 'TypeRepMap's.
389+-- It keeps all values from the first map whose keys are present in the second.
390+--
391+-- @O(n + m)@
392+intersection :: TypeRepMap f -> TypeRepMap f -> TypeRepMap f
393+intersection = intersectionWith const
394+{-# INLINE intersection #-}
395+
396+
397+{- | Check if a value of the given type is present in a 'TypeRepMap'.
398+
399+>>> member @Char $ one (Identity 'a')
400+True
401+>>> member @Bool $ one (Identity 'a')
402+False
403+-}
404+member :: forall a (f :: KindOf a -> Type) . Typeable a => TypeRepMap f -> Bool
405+member tm = case lookup @a tm of
406+ Nothing -> False
407+ Just _ -> True
408+{-# INLINE member #-}
409+
410+{- | Lookup a value of the given type in a 'TypeRepMap'.
411+
412+>>> x = lookup $ insert (Identity (11 :: Int)) empty
413+>>> x :: Maybe (Identity Int)
414+Just (Identity 11)
415+>>> x :: Maybe (Identity ())
416+Nothing
417+-}
418+lookup :: forall a f . Typeable a => TypeRepMap f -> Maybe (f a)
419+lookup tVect = fromAny . (trAnys tVect `indexArray`)
420+ <$> cachedBinarySearch (typeFp @a)
421+ (fingerprintAs tVect)
422+ (fingerprintBs tVect)
423+{-# INLINE lookup #-}
424+
425+-- | Get the amount of elements in a 'TypeRepMap'.
426+size :: TypeRepMap f -> Int
427+size = sizeofPrimArray . fingerprintAs
428+{-# INLINE size #-}
429+
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+
448+-- | Binary searched based on this article
449+-- http://bannalia.blogspot.com/2015/06/cache-friendly-binary-search.html
450+-- with modification for our two-vector search case.
451+cachedBinarySearch :: Fingerprint -> PrimArray Word64 -> PrimArray Word64 -> Maybe Int
452+cachedBinarySearch (Fingerprint (W64# a) (W64# b)) fpAs fpBs = inline (go 0#)
453+ where
454+ go :: Int# -> Maybe Int
455+ go i = case i <# len of
456+ 0# -> Nothing
457+ _ -> let !(W64# valA) = indexPrimArray fpAs (I# i) in case a `ltWord#` valA of
458+ 0# -> case a `eqWord#` valA of
459+ 0# -> go (2# *# i +# 2#)
460+ _ -> let !(W64# valB) = indexPrimArray fpBs (I# i) in case b `eqWord#` valB of
461+ 0# -> case b `ltWord#` valB of
462+ 0# -> go (2# *# i +# 2#)
463+ _ -> go (2# *# i +# 1#)
464+ _ -> Just (I# i)
465+ _ -> go (2# *# i +# 1#)
466+
467+ len :: Int#
468+ len = let !(I# l) = sizeofPrimArray fpAs in l
469+{-# INLINE cachedBinarySearch #-}
470+
471+----------------------------------------------------------------------------
472+-- Internal functions
473+----------------------------------------------------------------------------
474+
475+toAny :: f a -> Any
476+toAny = unsafeCoerce
477+
478+fromAny :: Any -> f a
479+fromAny = unsafeCoerce
480+
481+anyToTypeRep :: Any -> TypeRep f
482+anyToTypeRep = unsafeCoerce
483+
484+typeFp :: forall a . Typeable a => Fingerprint
485+typeFp = typeRepFingerprint $ typeRep @a
486+{-# INLINE typeFp #-}
487+
488+toTriples :: TypeRepMap f -> [(Fingerprint, Any, Any)]
489+toTriples tm = zip3 (toFingerprints tm) (GHC.toList $ trAnys tm) (GHC.toList $ trKeys tm)
490+
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)
508+
509+fst3 :: (a, b, c) -> a
510+fst3 (a, _, _) = a
511+
512+----------------------------------------------------------------------------
513+-- Functions for testing and benchmarking
514+----------------------------------------------------------------------------
515+
516+-- | Existential wrapper around 'Typeable' indexed by @f@ type parameter.
517+-- Useful for 'TypeRepMap' structure creation form list of 'WrapTypeable's.
518+data WrapTypeable f where
519+ WrapTypeable :: Typeable a => f a -> WrapTypeable f
520+
521+instance Show (WrapTypeable f) where
522+ show (WrapTypeable (_ :: f a)) = show $ calcFp @a
523+
524+{- |
525+
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]
532+
533+
534+-}
535+instance IsList (TypeRepMap f) where
536+ type Item (TypeRepMap f) = WrapTypeable f
537+
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
543+
544+ an :: WrapTypeable f -> Any
545+ an (WrapTypeable x) = toAny x
546+
547+ k :: WrapTypeable f -> Any
548+ k (WrapTypeable (_ :: f a)) = unsafeCoerce $ typeRep @a
549+
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)
561+ where
562+ (fpAs, fpBs) = unzip $ map (\(Fingerprint a b) -> (a, b)) fps
563+ (fps, ans, ks) = unzip3 $ fromSortedList kvs
564+
565+----------------------------------------------------------------------------
566+-- Tree-like conversion
567+----------------------------------------------------------------------------
568+
569+fromSortedList :: forall a . [a] -> [a]
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
576+ where
577+ -- state monad could be used here, but it's another dependency
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−9
@@ -1,11 +1,6 @@
1−resolver: lts-9.18
2−
3−packages:
4−- .
1+resolver: lts-15.8
52
63extra-deps:
7−- tasty-discover-4.1.1
8−
9−flags: {}
10−
11−extra-package-dbs: []
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.hsnew file+43
@@ -0,0 +1,43 @@
1+module Test.TypeRep.CMap
2+ ( cMapSpec
3+ ) where
4+
5+import Prelude hiding (lookup)
6+
7+import Data.Functor.Identity (Identity (..))
8+import Test.Hspec (Spec, describe, it, shouldBe)
9+
10+import Data.TypeRep.CMap (TypeRepMap, empty, insert, lookup, size)
11+
12+
13+-- | Simple test for 'lookup', 'insert' and 'size' functions.
14+cMapSpec :: Spec
15+cMapSpec = describe "Containers Map TypeRep" $ do
16+ describe "Lookup Test" $ do
17+ it "returns the inserted element" $
18+ lookup (insert (Identity 'a') empty) `shouldBe` Just (Identity 'a')
19+ it "returns the second inserted value of the same type" $
20+ lookup (insert (Identity 'b') $ insert (Identity 'a') empty) `shouldBe` Just (Identity 'b')
21+
22+ describe "Size Test" $ do
23+ it "is empty" $
24+ size empty `shouldBe` 0
25+ it "is of size 1 when 1 element inserted" $
26+ size (insert (Identity 'a') empty) `shouldBe` 1
27+ it "doesn't increase size when element of the same type is added" $
28+ size (insert (Identity 'b') $ insert (Identity 'a') empty) `shouldBe` 1
29+ it "returns 10 when 10 different types are inserted" $
30+ size mapOf10 `shouldBe` 10
31+
32+
33+mapOf10 :: TypeRepMap Identity
34+mapOf10 = insert (Identity True)
35+ $ insert (Identity [True, False])
36+ $ insert (Identity $ Just True)
37+ $ insert (Identity $ Just ())
38+ $ insert (Identity [()])
39+ $ insert (Identity ())
40+ $ insert (Identity @String "aaa")
41+ $ insert (Identity $ Just 'a')
42+ $ insert (Identity 'a')
43+ $ insert (Identity (11 :: Int)) empty
test/Test/TypeRep/Map.hsdeleted−33
@@ -1,33 +0,0 @@
1−module Test.TypeRep.Map where
2−
3−import Prelude hiding (lookup)
4−
5−import Test.Tasty.Hspec
6−
7−import Data.TypeRep.Map
8−
9−-- Simple test for 'lookup', 'insert' and 'size' functions.
10−spec_insertLookup :: Spec
11−spec_insertLookup = do
12− describe "Lookup Test" $ do
13− it "returns the inserted element" $
14− lookup (insert 'a' empty) `shouldBe` Just 'a'
15− it "returns the second inserted value of the same type" $
16− lookup (insert 'b' $ insert 'a' empty) `shouldBe` Just 'b'
17−
18− describe "Size Test" $ do
19− it "is empty" $
20− size empty `shouldBe` 0
21− it "is of size 1 when 1 element inserted" $
22− size (insert 'a' empty) `shouldBe` 1
23− it "doesn't increase size when element of the same type is added" $
24− size (insert 'b' $ insert 'a' empty) `shouldBe` 1
25− it "returns 10 when 10 different types are inserted" $
26− size mapOf10 `shouldBe` 10
27−
28−
29−mapOf10 :: TypeRepMap
30−mapOf10 = insert True $ insert [True, False] $ insert (Just True)
31− $ insert (Just ()) $ insert [()] $ insert ()
32− $ insert "aaa" $ insert (Just 'a') $ insert 'a'
33− $ insert (11 :: Int) empty
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.hsnew file+20
@@ -0,0 +1,20 @@
1+module Test.TypeRep.Vector
2+ ( vectorSpec
3+ ) where
4+
5+import Prelude hiding (lookup)
6+
7+import Data.Functor.Identity (Identity (..))
8+import Test.Hspec (Spec, describe, it, shouldBe)
9+
10+import Data.TypeRep.Vector (TF (..), fromList, lookup)
11+
12+
13+-- | Simple test for 'lookup', 'insert' and 'size' functions.
14+vectorSpec :: Spec
15+vectorSpec = describe "Vector TypeRep" $
16+ describe "Lookup Test" $ do
17+ it "returns the inserted element" $
18+ lookup (fromList [TF (Identity 'a')]) `shouldBe` Just (Identity 'a')
19+ it "returns the second inserted value of the same type" $
20+ lookup (fromList [TF (Identity 'b'), TF (Identity 'a')]) `shouldBe` Just (Identity 'b')
test/Test/TypeRep/VectorOpt.hsnew file+20
@@ -0,0 +1,20 @@
1+module Test.TypeRep.VectorOpt
2+ ( optimalVectorSpec
3+ ) where
4+
5+import Prelude hiding (lookup)
6+
7+import Data.Functor.Identity (Identity (..))
8+import Test.Hspec (Spec, describe, it, shouldBe)
9+
10+import Data.TypeRep.OptimalVector (TF (..), fromList, lookup)
11+
12+
13+-- | Simple test for 'lookup', 'insert' and 'size' functions.
14+optimalVectorSpec :: Spec
15+optimalVectorSpec = describe "Optimal Vector TypeRep" $
16+ describe "Lookup Test" $ do
17+ it "returns the inserted element" $
18+ lookup (fromList [TF $ Identity 'a']) `shouldBe` Just (Identity 'a')
19+ it "returns the second inserted value of the same type" $
20+ lookup (fromList [TF (Identity 'b'), TF (Identity 'a')]) `shouldBe` Just (Identity 'b')
typerep-extra-impls/Data/TypeRep/CMap.hsnew file+61
@@ -0,0 +1,61 @@
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+-}
10+
11+module Data.TypeRep.CMap
12+ ( TypeRepMap (..)
13+ , empty
14+ , insert
15+ , keys
16+ , lookup
17+ , size
18+ ) where
19+
20+import Prelude hiding (lookup)
21+
22+import Control.DeepSeq
23+import Data.Kind (Type)
24+import Data.Proxy (Proxy (..))
25+import Data.Typeable (TypeRep, Typeable, typeRep)
26+import GHC.Base (Any)
27+import Unsafe.Coerce (unsafeCoerce)
28+
29+import qualified Data.Map.Lazy as LMap
30+
31+
32+-- | Map-like data structure with types served as the keys.
33+newtype TypeRepMap (f :: k -> Type) = TypeRepMap
34+ { unMap :: LMap.Map TypeRep Any
35+ }
36+
37+instance NFData (TypeRepMap f) where
38+ rnf x = rnf (keys x) `seq` ()
39+
40+-- | Empty structure.
41+empty :: TypeRepMap f
42+empty = TypeRepMap mempty
43+
44+-- | Inserts the value with its type as a key.
45+insert :: forall a f . Typeable a => f a -> TypeRepMap f -> TypeRepMap f
46+insert val = TypeRepMap . LMap.insert (typeRep (Proxy :: Proxy a)) (unsafeCoerce val) . unMap
47+
48+-- | Looks up the value at the type.
49+-- >>> let x = lookup $ insert (11 :: Int) empty
50+-- >>> x :: Maybe Int
51+-- Just 11
52+-- >>> x :: Maybe ()
53+-- Nothing
54+lookup :: forall a f . Typeable a => TypeRepMap f -> Maybe (f a)
55+lookup = fmap unsafeCoerce . LMap.lookup (typeRep (Proxy :: Proxy a)) . unMap
56+
57+size :: TypeRepMap f -> Int
58+size = LMap.size . unMap
59+
60+keys :: TypeRepMap f -> [TypeRep]
61+keys = LMap.keys . unMap
typerep-extra-impls/Data/TypeRep/OptimalVector.hsnew file+127
@@ -0,0 +1,127 @@
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+-}
14+
15+module Data.TypeRep.OptimalVector
16+ ( -- * Map type
17+ TypeRepMap (..)
18+
19+ -- 'TypeRepMap' interface
20+ , empty
21+ , insert
22+ , lookup
23+ , size
24+
25+ -- * Helpful testing functions
26+ , TF (..)
27+ , fromList
28+ ) where
29+
30+import Prelude hiding (lookup)
31+
32+import Control.Arrow ((&&&))
33+import Control.DeepSeq
34+import Data.Kind (Type)
35+import Data.Proxy (Proxy (..))
36+import Data.Typeable (Typeable, typeRep, typeRepFingerprint)
37+import Data.Word (Word64)
38+import GHC.Base (Any, Int (..), Int#, uncheckedIShiftRA#, (+#), (-#), (<#))
39+import GHC.Exts (inline, sortWith)
40+import GHC.Fingerprint (Fingerprint (..))
41+import Unsafe.Coerce (unsafeCoerce)
42+
43+import qualified Data.Vector as V
44+import qualified Data.Vector.Unboxed as Unboxed
45+
46+
47+data TypeRepMap (f :: k -> Type) = TypeRepMap
48+ { fingerprintAs :: Unboxed.Vector Word64
49+ , fingerprintBs :: Unboxed.Vector Word64
50+ , anys :: V.Vector Any
51+ }
52+
53+instance NFData (TypeRepMap f) where
54+ rnf x = x `seq` ()
55+
56+fromAny :: Any -> f a
57+fromAny = unsafeCoerce
58+
59+-- | Empty structure.
60+empty :: TypeRepMap f
61+empty = TypeRepMap mempty mempty mempty
62+
63+-- | Inserts the value with its type as a key.
64+insert :: forall a f . a -> TypeRepMap f -> TypeRepMap f
65+insert = undefined
66+
67+-- | Looks up the value at the type.
68+-- >>> let x = lookup $ insert (11 :: Int) empty
69+-- >>> x :: Maybe Int
70+-- Just 11
71+-- >>> x :: Maybe ()
72+-- Nothing
73+lookup :: forall a f . Typeable a => TypeRepMap f -> Maybe (f a)
74+lookup tVect = fromAny . (anys tVect V.!)
75+ <$> binarySearch (typeRepFingerprint $ typeRep $ Proxy @a)
76+ (fingerprintAs tVect)
77+ (fingerprintBs tVect)
78+
79+-- | Returns the size of the 'TypeRepMap'.
80+size :: TypeRepMap f -> Int
81+size = Unboxed.length . fingerprintAs
82+
83+-- | Returns the index is found.
84+binarySearch :: Fingerprint -> Unboxed.Vector Word64 -> Unboxed.Vector Word64 -> Maybe Int
85+binarySearch (Fingerprint a b) fpAs fpBs =
86+ let
87+ !(I# len) = Unboxed.length fpAs
88+ checkfpBs :: Int# -> Maybe Int
89+ checkfpBs i =
90+ case i <# len of
91+ 0# -> Nothing
92+ _ | a /= Unboxed.unsafeIndex fpAs (I# i) -> Nothing
93+ | b == Unboxed.unsafeIndex fpBs (I# i) -> Just (I# i)
94+ | otherwise -> checkfpBs (i +# 1#)
95+ in
96+ inline (checkfpBs (binSearchHelp (-1#) len))
97+ where
98+ binSearchHelp :: Int# -> Int# -> Int#
99+ binSearchHelp l r = case l <# (r -# 1#) of
100+ 0# -> r
101+ _ ->
102+ let m = uncheckedIShiftRA# (l +# r) 1# in
103+ if Unboxed.unsafeIndex fpAs (I# m) < a
104+ then binSearchHelp m r
105+ else binSearchHelp l m
106+
107+----------------------------------------------------------------------------
108+-- Functions for testing and benchmarking
109+----------------------------------------------------------------------------
110+
111+data TF f where
112+ TF :: Typeable a => f a -> TF f
113+
114+fromF :: f a -> Proxy a
115+fromF _ = Proxy
116+
117+fromList :: forall f . [TF f] -> TypeRepMap f
118+fromList tfs = TypeRepMap (Unboxed.fromList fpAs) (Unboxed.fromList fpBs) (V.fromList ans)
119+ where
120+ (fpAs, fpBs) = unzip $ fmap (\(Fingerprint a b) -> (a, b)) fps
121+ (fps, ans) = unzip $ sortWith fst $ map (fp &&& an) tfs
122+
123+ fp :: TF f -> Fingerprint
124+ fp (TF x) = typeRepFingerprint $ typeRep $ fromF x
125+
126+ an :: TF f -> Any
127+ an (TF x) = unsafeCoerce x
typerep-extra-impls/Data/TypeRep/Vector.hsnew file+197
@@ -0,0 +1,197 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+
3+{-# LANGUAGE BangPatterns #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE MagicHash #-}
6+{-# LANGUAGE MultiParamTypeClasses #-}
7+{-# LANGUAGE PolyKinds #-}
8+{-# LANGUAGE TypeFamilies #-}
9+
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+
18+module Data.TypeRep.Vector
19+ ( TypeRepVector (..)
20+ , TF (..)
21+ , empty
22+ , insert
23+ , lookup
24+ , size
25+ , fromList
26+ ) where
27+
28+import Prelude hiding (lookup)
29+
30+import Control.Arrow ((&&&))
31+import Data.Proxy (Proxy (..))
32+import Data.Typeable (Typeable, typeRep, typeRepFingerprint)
33+import Data.Word (Word64)
34+import GHC.Base hiding (empty)
35+import GHC.Exts (sortWith)
36+import GHC.Fingerprint (Fingerprint (..))
37+import Unsafe.Coerce (unsafeCoerce)
38+
39+import qualified Data.Vector as V
40+import qualified Data.Vector.Generic as G
41+import qualified Data.Vector.Generic.Mutable as M
42+import qualified Data.Vector.Unboxed as Unboxed
43+
44+
45+data instance Unboxed.MVector s Fingerprint = MFingerprintVector (Unboxed.MVector s Word64) (Unboxed.MVector s Word64)
46+data instance Unboxed.Vector Fingerprint = FingerprintVector (Unboxed.Vector Word64) (Unboxed.Vector Word64)
47+
48+instance Unboxed.Unbox Fingerprint
49+
50+instance M.MVector Unboxed.MVector Fingerprint where
51+ {-# INLINE basicLength #-}
52+ basicLength (MFingerprintVector x _) = M.basicLength x
53+ {-# INLINE basicUnsafeSlice #-}
54+ basicUnsafeSlice i m (MFingerprintVector a b) =
55+ MFingerprintVector (M.basicUnsafeSlice i m a) (M.basicUnsafeSlice i m b)
56+ {-# INLINE basicOverlaps #-}
57+ basicOverlaps (MFingerprintVector as1 bs1) (MFingerprintVector as2 bs2) =
58+ M.basicOverlaps as1 as2 || M.basicOverlaps bs1 bs2
59+ {-# INLINE basicUnsafeNew #-}
60+ basicUnsafeNew n_ = do
61+ as <- M.basicUnsafeNew n_
62+ bs <- M.basicUnsafeNew n_
63+ return $ MFingerprintVector as bs
64+ {-# INLINE basicInitialize #-}
65+ basicInitialize (MFingerprintVector as bs) = do
66+ M.basicInitialize as
67+ M.basicInitialize bs
68+ {-# INLINE basicUnsafeReplicate #-}
69+ basicUnsafeReplicate n_ (Fingerprint a b) = do
70+ as <- M.basicUnsafeReplicate n_ a
71+ bs <- M.basicUnsafeReplicate n_ b
72+ return $ MFingerprintVector as bs
73+ {-# INLINE basicUnsafeRead #-}
74+ basicUnsafeRead (MFingerprintVector as bs) i_ = do
75+ a <- M.basicUnsafeRead as i_
76+ b <- M.basicUnsafeRead bs i_
77+ return (Fingerprint a b)
78+ {-# INLINE basicUnsafeWrite #-}
79+ basicUnsafeWrite (MFingerprintVector as bs) i_ (Fingerprint a b) = do
80+ M.basicUnsafeWrite as i_ a
81+ M.basicUnsafeWrite bs i_ b
82+ {-# INLINE basicClear #-}
83+ basicClear (MFingerprintVector as bs) = do
84+ M.basicClear as
85+ M.basicClear bs
86+ {-# INLINE basicSet #-}
87+ basicSet (MFingerprintVector as bs) (Fingerprint a b) = do
88+ M.basicSet as a
89+ M.basicSet bs b
90+ {-# INLINE basicUnsafeCopy #-}
91+ basicUnsafeCopy (MFingerprintVector as1 bs1) (MFingerprintVector as2 bs2) = do
92+ M.basicUnsafeCopy as1 as2
93+ M.basicUnsafeCopy bs1 bs2
94+ {-# INLINE basicUnsafeMove #-}
95+ basicUnsafeMove (MFingerprintVector as1 bs1) (MFingerprintVector as2 bs2) = do
96+ M.basicUnsafeMove as1 as2
97+ M.basicUnsafeMove bs1 bs2
98+ {-# INLINE basicUnsafeGrow #-}
99+ basicUnsafeGrow (MFingerprintVector as bs) m_ = do
100+ as' <- M.basicUnsafeGrow as m_
101+ bs' <- M.basicUnsafeGrow bs m_
102+ return $ MFingerprintVector as' bs'
103+
104+instance G.Vector Unboxed.Vector Fingerprint where
105+ {-# INLINE basicUnsafeFreeze #-}
106+ basicUnsafeFreeze (MFingerprintVector as bs) = do
107+ as' <- G.basicUnsafeFreeze as
108+ bs' <- G.basicUnsafeFreeze bs
109+ return $ FingerprintVector as' bs'
110+ {-# INLINE basicUnsafeThaw #-}
111+ basicUnsafeThaw (FingerprintVector as bs) = do
112+ as' <- G.basicUnsafeThaw as
113+ bs' <- G.basicUnsafeThaw bs
114+ return $ MFingerprintVector as' bs'
115+ {-# INLINE basicLength #-}
116+ basicLength (FingerprintVector x _) = G.basicLength x
117+ {-# INLINE basicUnsafeSlice #-}
118+ basicUnsafeSlice i_ m_ (FingerprintVector as bs) =
119+ FingerprintVector (G.basicUnsafeSlice i_ m_ as) (G.basicUnsafeSlice i_ m_ bs)
120+ {-# INLINE basicUnsafeIndexM #-}
121+ basicUnsafeIndexM (FingerprintVector as bs) i_ = do
122+ a <- G.basicUnsafeIndexM as i_
123+ b <- G.basicUnsafeIndexM bs i_
124+ return (Fingerprint a b)
125+ {-# INLINE basicUnsafeCopy #-}
126+ basicUnsafeCopy (MFingerprintVector as1 bs1) (FingerprintVector as2 bs2) = do
127+ G.basicUnsafeCopy as1 as2
128+ G.basicUnsafeCopy bs1 bs2
129+ {-# INLINE elemseq #-}
130+ elemseq _ (Fingerprint a b)
131+ = G.elemseq (undefined :: Unboxed.Vector a) a
132+ . G.elemseq (undefined :: Unboxed.Vector b) b
133+
134+data TypeRepVector f = TypeRepVect
135+ { fingerprints :: Unboxed.Vector Fingerprint
136+ , anys :: V.Vector Any
137+ }
138+
139+fromAny :: Any -> f a
140+fromAny = unsafeCoerce
141+
142+-- | Empty structure.
143+empty :: TypeRepVector f
144+empty = TypeRepVect mempty mempty
145+
146+-- | Inserts the value with its type as a key.
147+insert :: forall a f . a -> TypeRepVector f -> TypeRepVector f
148+insert = undefined
149+
150+-- | Looks up the value at the type.
151+-- >>> let x = lookup $ insert (11 :: Int) empty
152+-- >>> x :: Maybe Int
153+-- Just 11
154+-- >>> x :: Maybe ()
155+-- Nothing
156+lookup :: forall a f . Typeable a => TypeRepVector f -> Maybe (f a)
157+lookup tVect = fromAny . (anys tVect V.!)
158+ <$> binarySearch (typeRepFingerprint (typeRep (Proxy :: Proxy a))) (fingerprints tVect)
159+
160+-- | Returns the size of the 'TypeRepVect'.
161+size :: TypeRepVector f -> Int
162+size = Unboxed.length . fingerprints
163+
164+data TF f where
165+ TF :: Typeable a => f a -> TF f
166+
167+fromF :: f a -> Proxy a
168+fromF _ = Proxy
169+
170+fromList :: forall f . [TF f] -> TypeRepVector f
171+fromList tfs = TypeRepVect (Unboxed.fromList fps) (V.fromList ans)
172+ where
173+ (fps, ans) = unzip $ sortWith fst $ map (fp &&& an) tfs
174+
175+ fp :: TF f -> Fingerprint
176+ fp (TF x) = typeRepFingerprint $ typeRep $ fromF x
177+
178+ an :: TF f -> Any
179+ an (TF x) = unsafeCoerce x
180+
181+-- | Returns the index is found.
182+binarySearch :: Fingerprint -> Unboxed.Vector Fingerprint -> Maybe Int
183+binarySearch fp fpVect =
184+ let
185+ !(I# len) = Unboxed.length fpVect
186+ ind = I# (binSearchHelp (-1#) len)
187+ in
188+ if fp == (fpVect Unboxed.! ind) then Just ind else Nothing
189+ where
190+ binSearchHelp :: Int# -> Int# -> Int#
191+ binSearchHelp l r = case l <# (r -# 1#) of
192+ 0# -> r
193+ _ ->
194+ let m = uncheckedIShiftRA# (l +# r) 1# in
195+ if Unboxed.unsafeIndex fpVect (I# m) < fp
196+ then binSearchHelp m r
197+ else binSearchHelp l m
typerep-map.cabal+114−31
@@ -1,50 +1,133 @@
1+cabal-version: 2.4
12name: typerep-map
2−version: 0.1.0.0
3−description: Efficient implementation of data type with types as keys
4−homepage: https://github.com/vrom911/typerep-map
5−bug-reports: https://github.com/vrom911/typerep-map/issues
6−license: MIT
3+version: 0.3.3.0
4+synopsis: Efficient implementation of a dependent map with types as keys
5+description:
6+ A dependent map from type representations to values of these types.
7+ .
8+ Here is an illustration of such a map:
9+ .
10+ > TMap
11+ > ---------------
12+ > Int -> 5
13+ > Bool -> True
14+ > Char -> 'x'
15+ .
16+ In addition to @TMap@, we provide @TypeRepMap@ parametrized by a
17+ @vinyl@-style interpretation. This data structure is equivalent to @DMap
18+ TypeRep@, but with significantly more efficient lookups.
19+
20+homepage: https://github.com/kowainik/typerep-map
21+bug-reports: https://github.com/kowainik/typerep-map/issues
22+license: MPL-2.0
723license-file: LICENSE
8−author: Veronika Romashkina
9−maintainer: vrom911@gmail.com
10−copyright: 2017 Veronika Romashkina
11−category: Data Structures
24+author: Veronika Romashkina, Vladislav Zavialov, Dmitrii Kovanikov
25+maintainer: Kowainik <xrom.xkov@gmail.com>
26+copyright: 2017-2020 Kowainik
27+category: Data, Data Structures, Types
1228build-type: Simple
13−extra-source-files: README.md
14−cabal-version: >=1.10
15−tested-with: GHC == 8.0.1
29+extra-doc-files: README.md
30+ , CHANGELOG.md
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
36+
37+source-repository head
38+ type: git
39+ location: https://github.com/kowainik/typerep-map.git
40+
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
1668
1769library
70+ import: common-options
1871 hs-source-dirs: src
19− exposed-modules: Data.TypeRep.Map
20− ghc-options: -Wall
21− build-depends: base
22− , containers
23− default-language: Haskell2010
72+ exposed-modules: Data.TMap
73+ Data.TypeRepMap
74+ Data.TypeRepMap.Internal
75+
76+ build-depends: ghc-prim >= 0.5.1.1 && < 0.7
77+ , primitive ^>= 0.7.0
78+ , deepseq ^>= 1.4
79+
80+library typerep-extra-impls
81+ import: common-options
82+ hs-source-dirs: typerep-extra-impls
83+ exposed-modules: Data.TypeRep.CMap
84+ Data.TypeRep.OptimalVector
85+ Data.TypeRep.Vector
86+
87+ build-depends: containers >= 0.5.10.2 && < 0.7
88+ , vector ^>= 0.12.0.1
89+ , deepseq ^>= 1.4
2490
2591test-suite typerep-map-test
92+ import: common-options
2693 type: exitcode-stdio-1.0
2794 hs-source-dirs: test
95+
2896 main-is: Test.hs
29− other-modules: Test.TypeRep.Map
30− build-depends: base
97+ other-modules: Test.TypeRep.CMap
98+ , Test.TypeRep.TypeRepMap
99+ , Test.TypeRep.TypeRepMapProperty
100+ , Test.TypeRep.Vector
101+ , Test.TypeRep.VectorOpt
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
31107 , typerep-map
32− , tasty
33− , tasty-discover >= 4.1.1
34− , tasty-hspec
35− ghc-options: -Wall -Werror -threaded -rtsopts -with-rtsopts=-N
36− default-language: Haskell2010
108+ , typerep-extra-impls
109+
110+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
37111
38112benchmark typerep-map-benchmark
113+ import: common-options
39114 type: exitcode-stdio-1.0
40− default-language: Haskell2010
41− ghc-options: -Wall -Werror -O2 -threaded -rtsopts -with-rtsopts=-N
42115 hs-source-dirs: benchmark
116+
43117 main-is: Main.hs
44− build-depends: base
45− , criterion
118+ other-modules: CMap
119+ , CacheMap
120+ , DMap
121+ , Spec
122+ , Vector
123+ , OptimalVector
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
46130 , typerep-map
131+ , typerep-extra-impls
47132
48−source-repository head
49− type: git
50− location: https://github.com/vrom911/typerep-map.git
133+ ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N -freduction-depth=0