bytestring-trie 0.2.5.3 → 0.2.7.6
raw patch · 20 files changed
Files
- CHANGELOG +93/−21
- README.md +8/−2
- bytestring-trie.cabal +162/−43
- dev/Bench/Foldable.hs +516/−0
- dev/Bench/MatchOne.hs +124/−0
- dev/Bench/Regression.hs +145/−0
- dev/Bench/UnionWith.hs +70/−0
- dev/Shared/BaseCompat.hs +200/−0
- dev/Test/Main.hs +197/−0
- dev/Test/Properties.hs +1245/−0
- dev/Test/Utils.hs +254/−0
- src/Data/Trie.hs +87/−60
- src/Data/Trie/BitTwiddle.hs +0/−171
- src/Data/Trie/ByteStringInternal.hs +0/−112
- src/Data/Trie/Convenience.hs +56/−29
- src/Data/Trie/Errors.hs +0/−30
- src/Data/Trie/Internal.hs +2390/−1047
- src/Data/Trie/Internal/BitTwiddle.hs +258/−0
- src/Data/Trie/Internal/ByteString.hs +346/−0
- src/Data/Trie/Internal/Errors.hs +50/−0
CHANGELOG view
@@ -1,32 +1,104 @@-0.2.5.3 (2021.11.02):+0.2.7.6 (2025-02-11):+ * Technical Debt:+ - Relaxed upper-bounds for GHC 9.12+ - Silencing some warnings on GHC 9.10+0.2.7.5 (2024-08-29):+ * Technical Debt:+ - Relaxed upper-bounds for the test suite+0.2.7.4 (2024-08-29):+ * Technical Debt:+ - Relaxed upper-bounds for GHC 9.10+ - Tightened lower-bounds to match only what CI still verifies.+0.2.7.3 (2024-04-05):+ * Technical Debt:+ - Updated for GHC 9.8+0.2.7.2 (2023-03-19):+ * Technical Debt:+ - Updated for GHC 9.6+0.2.7.1 (2022-08-28):+ * Technical Debt:+ - Updated for GHC 9.4+0.2.7 (2022-03-14):+ * Added:+ - Instances: Eq1, Ord, Ord1, IsList, Show1, Read, Read1, NFData.+ - Methods: Semigroup(stimes), Foldable(fold,foldMap')+ - Functions: Data.Trie.Internal.{filter,filterA,wither}+ * Performance Improvements:+ - Significantly sped up `size`+ - Misc other changes to the Foldable instance.+ - Reordered data constructors, for better branch-prediction.+ - Fixed quadratic slowdown for priority-queue functions.+ - Reduced quadratic slowdown for functions that reconstruct keys;+ however, it's still asymptotically quadratic:+ <https://github.com/wrengr/bytestring-trie/issues/25>+ * Breakage:+ - Changed the type of Data.Trie.Internal.lookupBy_+ * Technical Debt:+ - Updated the *.cabal file to use Cabal-2.2 format/features.+ - Greatly increased code-coverage of the test suite+ - Added a bunch of benchmarks, including for regression testing.+ - Added Safe/Trustworthy annotations.+ - Cleaning up OPTIONS_GHC and imports.+ - Cleaning up Haddock and adding more @since annotations.+0.2.6 (2021-11-22):+ * Added:+ - Data.Trie.minMatch+ - Data.Trie.Internal.{cata,cata_}+ - intersection functions (HT: Kevin Brubeck Unhammer)+ - Data.Trie.deleteSubmap (HT: YongJoon Joe)+ * Fixed:+ - Adjusting the Applicative, Monad, and Monoid instances to+ avoid warnings from GHC 9.2.1 regarding:+ <https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of-no-return>+ <https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/semigroup-monoid>+ - Fixed a bug in mergeBy+ * Documentation:+ - Documented the order of results for `matches`/`matches_`+ * Technical Debt:+ - Numerous minor tweaks with smart-constructors+ - Greatly increased code-coverage of the test suite+0.2.5.3 (2021-11-02):+ * Fixed: - Increasing upper bounds for GHC 9.2.1-0.2.5.2 (2021.10.16):+0.2.5.2 (2021-10-16):+ * Fixed: - Increasing upper bounds to Hackage latest, instead of just GHC 9.0.1-0.2.5.1 (2021.10.16):+0.2.5.1 (2021-10-16):+ * Fixed: - Fixing things to compile under GHC 9.0.1-0.2.5.0 (2019.02.25):+0.2.5.0 (2019-02-25):+ * Added:+ - Instance: Semigroup+ * Fixed: - Fixing things to compile under GHC 8.4 and 8.6.- - Adds Semigroup (Trie a) instance+ * Technical Debt: - Removed the obsolete/unused "useCinternal" Cabal flag-0.2.4.3 (2019.02.24):+0.2.4.3 (2019-02-24):+ * Technical Debt: - Moved VERSION to CHANGELOG- - Fixing things to compile under GHC 8.0 and 8.2. N.B., still doesn't compile under 8.4 or 8.6, due to the version limit on `base`.-0.2.4.1 (2015.04.04):- - Data.Trie.Internal: adjusted imports to compule under GHC 7.10.1-0.2.4 (2014.10.09):- - added Data.Trie.Internal.{match_,matches_}, Data.Trie.Base.{match,matches}-0.2.3 (2010.02.12):- - added Data.Trie.Internal.alterBy_- - added Data.Trie.Internal.{contextualMap, contextualMap', contextualFilterMap, contextualMapBy}- - added Data.Trie.Convenience.{fromListWith', fromListWithL, fromListWithL'} as suggested by Ian Taylor- - added Data.Trie.Convenience{insertWith', insertWithKey', unionWith'}+ * Fixed:+ - Fixing things to compile under GHC 8.0 and 8.2. N.B., still+ doesn't compile under 8.4 or 8.6, due to the version limit on `base`.+0.2.4.1 (2015-04-04):+ * Fixed:+ - Data.Trie.Internal: adjusted imports to compile under GHC 7.10.1+0.2.4 (2014-10-09):+ * Added:+ - Data.Trie.Internal.{match_,matches_}, Data.Trie.{match,matches}+0.2.3 (2010-02-12):+ * Added:+ - Data.Trie.Internal.alterBy_+ - Data.Trie.Internal.{contextualMap, contextualMap', contextualFilterMap, contextualMapBy}+ - Data.Trie.Convenience.{fromListWith', fromListWithL, fromListWithL'} as suggested by Ian Taylor+ - Data.Trie.Convenience.{insertWith', insertWithKey', unionWith'}+ * Performance: - converted fmap, foldMap, traverse, and filterMap to worker/wrapper-0.2.2 (2010.06.10):+0.2.2 (2010-06-10):+ * Fixed: - Corrected a major bug in mergeBy, reported by Gregory Crosswhite-0.2.1.1 (2009.12.20):+0.2.1.1 (2009-12-20): - Added a VERSION file-0.2.1 (2009.02.13):+0.2.1 (2009-02-13): - Most recent release before adding a VERSION file--0.1.4 (2009.01.11):+0.1.4 (2009-01-11): - The only previous tag
README.md view
@@ -33,11 +33,17 @@ ## Portability The implementation is quite portable, relying only on a few basic-language extensions. The complete list of extensions used is:+language extensions. The complete list of extensions used by the library is: * CPP-* MagicHash +* MagicHash -- Only if using GHC * NoImplicitPrelude++The test suite uses a few additional extensions:++* MultiParamTypeClasses+* FlexibleInstances+* FlexibleContexts ## Links
bytestring-trie.cabal view
@@ -1,49 +1,55 @@+Cabal-Version: 2.2+-- Cabal >=2.2 is required for:+-- <https://cabal.readthedocs.io/en/latest/cabal-package.html#common-stanzas>+-- Since 2.1, the Cabal-Version must be the absolutely first thing+-- in the file, even before comments. Also, no longer uses ">=".+-- <https://github.com/haskell/cabal/issues/4899>+ ------------------------------------------------------------------- wren gayle romano <wren@cpan.org> ~ 2021.11.02+-- wren gayle romano <wren@cpan.org> ~ 2025-02-11 ---------------------------------------------------------------- --- Cabal >=1.10 is required by Hackage.-Cabal-Version: >= 1.10-Build-Type: Simple- Name: bytestring-trie-Version: 0.2.5.3+Version: 0.2.7.6+Build-Type: Simple Stability: provisional Homepage: https://wrengr.org/software/hackage.html Bug-Reports: https://github.com/wrengr/bytestring-trie/issues Author: wren gayle romano Maintainer: wren@cpan.org-Copyright: Copyright (c) 2008–2021 wren gayle romano-License: BSD3+Copyright: 2008–2025 wren gayle romano+-- Cabal-2.2 requires us to say "BSD-3-Clause" not "BSD3"+License: BSD-3-Clause License-File: LICENSE Category: Data, Data Structures-Synopsis: An efficient finite map from (byte)strings to values.-Description: An efficient finite map from (byte)strings to values.- .- The implementation is based on big-endian patricia- trees, like "Data.IntMap". We first trie on the- elements of "Data.ByteString" and then trie on the- big-endian bit representation of those elements.- Patricia trees have efficient algorithms for union- and other merging operations, but they're also quick- for lookups and insertions.- .- If you are only interested in being able to associate- strings to values, then you may prefer the @hashmap@- package which is faster for those only needing a- map-like structure. This package is intended for- those who need the extra capabilities that a trie-like- structure can offer (e.g., structure sharing to- reduce memory costs for highly redundant keys,- taking the submap of all keys with a given prefix,- contextual mapping, extracting the minimum and- maximum keys, etc.)+Synopsis: An efficient finite map from bytestrings to values.+Description: An efficient finite map from bytestrings to values.+ .+ The implementation is based on big-endian patricia trees, like+ "Data.IntMap". We first trie on the elements of "Data.ByteString"+ and then trie on the big-endian bit representation of those+ elements. Patricia trees have efficient algorithms for union+ and other merging operations, but they're also quick for lookups+ and insertions.+ .+ If you are only interested in being able to associate strings+ to values, then you may prefer the @hashmap@ package which is+ faster for those only needing a map-like structure. This package+ is intended for those who need the extra capabilities that a+ trie-like structure can offer (e.g., structure sharing to reduce+ memory costs for highly redundant keys, taking the submap of+ all keys with a given prefix, contextual mapping, extracting+ the minimum and maximum keys, etc.) Extra-source-files: AUTHORS, CHANGELOG, README.md --- This should work as far back as GHC 7.4.1, but we don't verify that by CI.+-- This package ought to work as far back as GHC 7.4.1; however,+-- I've tightened the lower bounds on the Build-Depends to match+-- only what we still verify via CI:+-- <https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/libraries/version-history>+-- <https://matrix.hackage.haskell.org/#/package/bytestring-lexing> -- <https://github.com/wrengr/bytestring-trie/actions?query=workflow%3Aci> Tested-With: GHC ==8.0.2,@@ -53,28 +59,141 @@ GHC ==8.8.4, GHC ==8.10.3, GHC ==9.0.1,- GHC ==9.2.1+ GHC ==9.2.4,+ GHC ==9.4.8,+ GHC ==9.6.5,+ GHC ==9.8.2,+ GHC ==9.10.1,+ GHC ==9.12.1 Source-Repository head Type: git Location: https://github.com/wrengr/bytestring-trie.git -----------------------------------------------------------------Library+-- This stanza requires Cabal>=2.2:+-- <https://cabal.readthedocs.io/en/latest/cabal-package.html#common-stanzas>+-- While Cabal-2.2 only ships with GHC 8.4.1, the dependencies to+-- build it have essentially the same lower bounds as we do. (They+-- require bytestring>=0.9.2.1 and deepseq>=1.3) So users of older+-- GHC should still be able to compile it; and if they can't, then+-- they already can't compile this package.+--+-- N.B., the "import:" field must be the first thing in a stanza.+Common library-build-depends Default-Language: Haskell2010+ Build-Depends: base >= 4.9 && < 4.22+ , bytestring >= 0.10.8 && < 0.13+ , binary >= 0.8.3 && < 0.9+ , deepseq >= 1.4.2 && < 1.6++-- TODO: in lieu of using CPP to expose internals to the tests/benchmarks,+-- we should consider using:+-- <https://cabal.readthedocs.io/en/latest/cabal-package.html#sublibs>++Library+ Import: library-build-depends Hs-Source-Dirs: src Exposed-Modules: Data.Trie- , Data.Trie.Internal , Data.Trie.Convenience- Other-Modules: Data.Trie.BitTwiddle- , Data.Trie.ByteStringInternal- , Data.Trie.Errors- -- The lower bounds are more restrictive than necessary.- -- But then, we don't maintain any CI tests for older- -- versions, so these are the lowest bounds we've verified.- Build-Depends: base >= 4.5 && < 4.17- , bytestring >= 0.9.2 && < 0.12- , binary >= 0.5.1 && < 0.11- + , Data.Trie.Internal+ Other-Modules: Data.Trie.Internal.BitTwiddle+ , Data.Trie.Internal.ByteString+ , Data.Trie.Internal.Errors++----------------------------------------------------------------+-- See the cabal file for bytestring-lexing for more info about+-- setting up Tasty.+Test-Suite test-all+ Import: library-build-depends+ Type: exitcode-stdio-1.0+ Hs-Source-Dirs: dev+ -- NOTE: Test-Suite Main-Is is relative to Hs-Source-Dirs;+ -- unlike Executable Main-Is, which is relative to this cabal+ -- file's directory instead.+ Main-Is: Test/Main.hs+ Other-Modules: Shared.BaseCompat+ , Test.Utils+ , Test.Properties+ -- Test.Validity+ -- Test.ByteStringInternal+ -- We must include our own library for the tests to use it; but+ -- we must not give a version restriction lest Cabal give warnings.+ -- There's also bug <https://github.com/haskell/cabal/issues/5119>:+ -- if we don't pass -any, then Cabal will fill in ">= 0 && <= $ThisVersion"+ -- which will also give a warning.+ Build-Depends: bytestring-trie -any+ , tasty >= 0.10.1.2 && < 1.6+ , tasty-smallcheck >= 0.8.0.1 && < 0.9+ , tasty-quickcheck >= 0.8.3.2 && < 0.12+ -- N.B., @tasty-hunit@ is just a partial API clone;+ -- whereas @tasty-hunit-compat@ is a proper integration+ -- with HUnit itself. Also, tasty-hunit-compat actually+ -- depends on tasty-hunit; if you're wanting to minimize+ -- dependencies.+ , tasty-hunit < 0.11+ , QuickCheck >= 2.10 && < 2.16+ , smallcheck >= 1.1.1 && < 1.3+ -- lazysmallcheck >= 0.6 && < 0.7++----------------------------------------------------------------+-- Can't put the "Type:" field in here; or rather even if we do,+-- it's still required in each of the Benchmark stanzas...+Common bench-common+ Import: library-build-depends+ if impl(ghc)+ GHC-Options: -with-rtsopts=-A32m+ -- TODO: this was recommended somewhere for benchmarks, but is+ -- --nonmoving-gc actually good for our use case? (It's+ -- concurrent-mark&sweep for the old generations; instead of+ -- the default stop-the-world generational copying collector)+ --if impl(ghc >= 8.10)+ -- GHC-Options: -with-rtsopts=--nonmoving-gc+ if impl(ghc)+ GHC-Options: -Wall -O2 -rtsopts+ Hs-Source-Dirs: dev+ Build-Depends: bytestring-trie -any+ -- TODO: try using @gauge@ instead of @criterion@,+ -- to reduce the transitive dependencies.+ -- BUG: @gauge@ depends on @basement@ which as of+ -- version 0.0.12 doesn't support GHC 9.2; so we'll+ -- have to revisit this later.+ --+ -- NOTE: If you're having issues building on ghc-9.6,+ -- then be sure to update your cabal info.+ -- The parallel-3.2.2.0 package has metadata revisions+ -- to allow building with base-4.18.+ , criterion+ , QuickCheck >= 2.10 && < 2.16++Benchmark bench-Regression+ Import: bench-common+ Type: exitcode-stdio-1.0+ -- NOTE: Benchmark Main-Is behaves like Test-Suite Main-Is+ -- (not like Executable Main-Is).+ Main-Is: Bench/Regression.hs+ if impl(ghc)+ GHC-Options: -main-is Bench.Regression.main+ Other-Modules: Shared.BaseCompat+ , Bench.Foldable++Benchmark bench-Foldable+ Import: bench-common+ Type: exitcode-stdio-1.0+ Main-Is: Bench/Foldable.hs+ if impl(ghc)+ GHC-Options: -main-is Bench.Foldable.main+ Other-Modules: Shared.BaseCompat++Benchmark bench-MatchOne+ Import: bench-common+ Type: exitcode-stdio-1.0+ Main-Is: Bench/MatchOne.hs++Benchmark bench-UnionWith+ Import: bench-common+ Type: exitcode-stdio-1.0+ Main-Is: Bench/UnionWith.hs+ ---------------------------------------------------------------- ----------------------------------------------------------- fin.
+ dev/Bench/Foldable.hs view
@@ -0,0 +1,516 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP, BangPatterns #-}++----------------------------------------------------------------+-- ~ 2022.03.04+-- |+-- Module : Bench.Foldable+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : portable (with bang-patterns)+--+-- Benchmarking definitions for 'Foldable'+----------------------------------------------------------------++module Bench.Foldable (main, realTrie_to_benchTrie, bgroup_Foldable) where++import Shared.BaseCompat+import qualified Data.Trie as T+import qualified Data.Trie.Internal as TI++import qualified Data.ByteString as S+#if MIN_VERSION_base(4,7,0)+-- [GHC 7.8.1]: "Data.Coerce" added to base.+import Data.Coerce (Coercible, coerce)+#endif+#if MIN_VERSION_base(4,9,0)+-- [GHC 8.0.1]: "Data.Semigroup" added to base.+import Data.Semigroup (Endo(..))+#endif+-- TODO: version limits for 'Dual'?+import Data.Monoid (Dual(..))+import Data.Word (Word8)+import Control.DeepSeq (NFData(rnf))+import qualified Test.QuickCheck as QC+import qualified Criterion.Main as C+-- TODO: consider using the @gauge@ library instead of @criterion@.+-- It's a clone of criterion, also by BOS; with the intention of+-- having reduced dependencies. It doesn't do the HTML output, but+-- otherwise should be fine for our needs. It's what the @containers@+-- library is using these days.+-- BUG: @gauge@ depends on @basement@ which as of version 0.0.12+-- doesn't support GHC 9.2; so we'll have to revisit this later.+----------------------------------------------------------------++-- | Reference definition, to avoid needing to expose constructors+-- of the real definition.+--+-- TODO: should maybe have a CPP macro to expose them for benchmarking,+-- to ensure this file stays in sync!+data Trie a+ = Branch {-# UNPACK #-} !Word8 -- Prefix+ {-# UNPACK #-} !Word8 -- Mask+ !(Trie a)+ !(Trie a)+ | Arc {-# UNPACK #-} !S.ByteString+ !(Maybe a)+ !(Trie a)+ | Empty+ deriving Show++-- Needed for 'C.env'+instance NFData a => NFData (Trie a) where+ rnf Empty = ()+ rnf (Arc _ mv t) = rnf mv `seq` rnf t+ rnf (Branch _ _ l r) = rnf l `seq` rnf r++----------------------------------------------------------------+-- | From "Data.Functor.Utils", but isn't exported. Is used heavily+-- by the default implementations, since they use so many newtype+-- wrappers.+(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)+(#.) _f = coerce+{-# INLINE (#.) #-}++----------------------------------------------------------------+fold_foldMap, fold_foldrCompose, fold_foldrEta, fold_v027+ :: Monoid m => Trie m -> m++-- | The default 'fold' definition as of base-4.16.0.0+fold_foldMap = foldMap_v027 id++-- | Default 'fold' via default 'foldMap' (using 'foldr_compose'),+-- but inlining the 'id' away.+-- Far worse than 'fold_foldMap', both for runtime and allocation.+fold_foldrCompose = foldr_compose mappend mempty++-- | Default 'fold' via default 'foldMap' (using 'foldr_eta'),+-- but inlining the 'id' away.+-- This is somewhat worse than 'fold_foldrCompose' even.+fold_foldrEta = foldr_eta mappend mempty++-- | bytestring-trie-0.2.7 definition.+-- This is a clear win over 'fold_foldMap': about half the runtime,+-- and one eighth the allocations.+fold_v027 = go+ where+ go Empty = mempty+ go (Arc _ Nothing t) = go t+ go (Arc _ (Just v) t) = v `mappend` go t+ go (Branch _ _ l r) = go l `mappend` go r++----------------------------------------------------------------+foldMap_foldrCompose, foldMap_foldrEta, foldMap_v027+ :: Monoid m => (a -> m) -> Trie a -> m++-- | The default 'fold' definition as of base-4.16.0.0 (using 'foldr_compose').+foldMap_foldrCompose f = foldr_compose (mappend . f) mempty+-- | The default 'fold' definition as of base-4.16.0.0 (using 'foldr_eta').+foldMap_foldrEta f = foldr_eta (mappend . f) mempty++-- | bytestring-trie-0.2.7 definition, also used elsewhere for defaults.+-- (This was also the bytestring-trie-0.2.6 definition; the only+-- thing defined for that version.)+-- Again, this is the clear winner over the default implementation.+foldMap_v027 f = go+ where+ go Empty = mempty+ go (Arc _ Nothing t) = go t+ go (Arc _ (Just v) t) = f v `mappend` go t+ go (Branch _ _ l r) = go l `mappend` go r++----------------------------------------------------------------+foldMap'_default, foldMap'_v027, foldMap'_v027_cps, foldMap'_v027_flop, foldMap'_v027_bang, foldMap'_v027_flop_bang+ :: Monoid m => (a -> m) -> Trie a -> m++-- | default definition.+foldMap'_default f = foldl'_v027 (\ acc a -> acc <> f a) mempty++-- | bytestring-trie-0.2.7 definition.+-- Again, this is the clear winner over the default implementation.+foldMap'_v027 f = go mempty+ where+ go !m Empty = m+ go m (Arc _ Nothing t) = go m t+ go m (Arc _ (Just v) t) = go (m `mappend` f v) t+ go m (Branch _ _ l r) = go (go m l) r++-- | CPS variant, to ensure proper tail-recursion.+-- In between 'foldMap'_v027' and 'foldMap'_default'.+foldMap'_v027_cps f = \t -> go t mempty id+ where+ go Empty !m c = c m+ go (Arc _ Nothing t) m c = go t m c+ go (Arc _ (Just v) t) m c = go t (m `mappend` f v) c+ go (Branch _ _ l r) m c = go l m (\m' -> go r m' c)++-- | Based on our experience with 'foldl_v027_flop', trying the flop here.+-- On the small tries tested here, is slightly slower than+-- 'foldMap'_v027'; identical allocation. On larger tries this+-- gets to be slightly better than 'foldMap'_v027'.+foldMap'_v027_flop f = \t -> go t mempty+ where+ go Empty !m = m+ go (Arc _ Nothing t) m = go t m+ go (Arc _ (Just v) t) m = go t (m `mappend` f v)+ go (Branch _ _ l r) m = go r (go l m)++-- | This really shouldn't matter, but given our experience with 'foldl'_v027_flop_bang'...+-- And yet, is slightly better than 'foldMap'_v027_flop', slightly+-- worse than 'foldMap'_v027_flop_bang'; identical allocation for+-- all these variants.+foldMap'_v027_bang f = go mempty+ where+ go !m Empty = m+ go m (Arc _ Nothing t) = go m t+ go m (Arc _ (Just v) t) = (go $! mappend m (f v)) t+ go m (Branch _ _ l r) = (go $! go m l) r++-- | This really shouldn't matter, but given our experience with 'foldl'_v027_flop_bang'...+-- And yet, is slightly better than 'foldMap'_v027_bang', slightly+-- worse than 'foldMap'_v027'; identical allocation for all these+-- variants. For larger tries, is slightly slower than 'foldMap'_v027_flop'.+foldMap'_v027_flop_bang f = \t -> go t mempty+ where+ go Empty !m = m+ go (Arc _ Nothing t) m = go t m+ go (Arc _ (Just v) t) m = go t $! mappend m (f v)+ go (Branch _ _ l r) m = go r $! go l m++----------------------------------------------------------------+foldr_default, foldr_compose, foldr_eta, foldr_cps_eta, foldr_cps, foldr_noClosure+ :: (a -> b -> b) -> b -> Trie a -> b++-- | The default definition as of base-4.16.0.0+-- Actually a pretty solid baseline, for small tries. And for+-- larger tries it's far and away the clear winner.+foldr_default f z t =+ appEndo (foldMap_v027 (Endo #. f) t) z++-- | bytestring-trie-0.2.7 definition+-- Identical allocation as EndoDefault; about the same speed, or a+-- slightly slower; but only for the small tries tested here. For+-- larger tries it's far worse than 'foldr_default'.+foldr_compose f z0 = \t -> go t z0 -- eta for better inlining.+ where+ go Empty = id+ go (Arc _ Nothing t) = go t+ go (Arc _ (Just v) t) = f v . go t+ go (Branch _ _ l r) = go l . go r++-- |+-- Usually little faster than EndoDefault, occasionally about the+-- same, rarely slower. However, this allocates (consistently) ~43% more.+-- [I'm thinking because (a) larger thunks to store the @z@, but+-- (b) fully-saturated thunks so faster apply?]+-- For larger tries, is also far worse than 'foldr_default'.+foldr_eta f z0 = \t -> go t z0 -- eta for better inlining.+ where+ go Empty z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = f v (go t z)+ go (Branch _ _ l r) z = go l (go r z)++-- | Much slower; also allocates much more. (For larger tries too)+foldr_cps_eta f z0 = \t -> go t id z0 -- eta for better inlining.+ where+ go Empty c z = c z+ go (Arc _ Nothing t) c z = go t c z+ go (Arc _ (Just v) t) c z = go t (c . f v) z+ go (Branch _ _ l r) c z = go r (go l c) z++-- | Even slower; and even more allocation. (For larger tries too)+foldr_cps f z0 = \t -> go t id z0 -- eta for better inlining.+ where+ go Empty c = c+ go (Arc _ Nothing t) c = go t c+ go (Arc _ (Just v) t) c = go t (c . f v)+ go (Branch _ _ l r) c = go r (go l c)++-- | Faster than 'foldr_cps_eta', but still much slower than+-- EndoDefault/compose/eta; Also allocates about as much as+-- 'foldr_cps_eta'.+foldr_noClosure _ z Empty = z+foldr_noClosure f z (Arc _ Nothing t) = foldr_noClosure f z t+foldr_noClosure f z (Arc _ (Just v) t) = f v (foldr_noClosure f z t)+foldr_noClosure f z (Branch _ _ l r) =+ foldr_noClosure f (foldr_noClosure f z r) l++----------------------------------------------------------------+foldr'_default, foldr'_v027, foldr'_v027_cps, foldr'_cps, foldr'_v027_flop, foldr'_v027_bang, foldr'_v027_flop_bang+ :: (a -> b -> b) -> b -> Trie a -> b++-- | The default definition as of base-4.16.0.0 (including the phrasing+-- of using a where-clause rather than a lambda).+-- The worst of the lot.+foldr'_default f z0 xs =+ foldl_v027 f' id xs z0+ where f' k x z = k $! f x z++-- | bytestring-trie-0.2.7 definition.+-- This one is the clear winner, both for time and allocation.+foldr'_v027 f z0 = go z0 -- eta for better inlining+ where+ go !z Empty = z+ go z (Arc _ Nothing t) = go z t+ go z (Arc _ (Just v) t) = f v $! go z t+ go z (Branch _ _ l r) = go (go z r) l++-- TODO: (2022.03.05) Try a variant of 'foldr'_v027' which doesn't+-- closure over @f@.++-- | Worse than 'foldr'_v027' but still better than the rest.+foldr'_v027_cps f z0 = \t -> go t z0 id -- eta for better inlining+ where+ go Empty !z c = c z+ go (Arc _ Nothing t) z c = go t z c+ go (Arc _ (Just v) t) z c = go t z (\ !z' -> c $! f v z')+ go (Branch _ _ l r) z c = go r z (\ !z' -> go l z' c)++-- | Worse than 'foldr'_v027_cps' but still better than 'foldr'_default'+foldr'_cps f z0 = \t -> go t id z0 -- eta for better inlining+ where+ go Empty c = c+ go (Arc _ Nothing t) c = go t c+ go (Arc _ (Just v) t) c = go t (\ !z -> c $! f v z)+ go (Branch _ _ l r) c = go r (go l c)++-- | Based on our experience with 'foldl_v027_flop', trying the flop here.+-- Actually faster than 'foldr'_v027' (but only for small tries);+-- identical allocation. For larger tries, this is a bit slower+-- than 'foldr'_v027'.+foldr'_v027_flop f z0 = \t -> go t z0 -- eta for better inlining+ where+ go Empty !z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = f v $! go t z+ go (Branch _ _ l r) z = go l (go r z)++-- | This really shouldn't matter, but given our experience with 'foldl'_v027_flop_bang'...+-- And yet, faster than 'foldr'_cps', slower than 'foldr'_v027_flop'.+foldr'_v027_bang f z0 = go z0 -- eta for better inlining+ where+ go !z Empty = z+ go z (Arc _ Nothing t) = go z t+ go z (Arc _ (Just v) t) = f v $! go z t+ go z (Branch _ _ l r) = (go $! go z r) l++-- | This really shouldn't matter, but given our experience with 'foldl'_v027_flop_bang'...+-- And yet, marginally faster than 'foldr'_v027_flop', more+-- significantly slower than 'foldr'_v027'.+foldr'_v027_flop_bang f z0 = \t -> go t z0 -- eta for better inlining+ where+ go Empty !z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = f v $! go t z+ go (Branch _ _ l r) z = go l $! go r z++----------------------------------------------------------------+foldl_default, foldl_default_Coerce, foldl_v027, foldl_v027_flop+ :: (b -> a -> b) -> b -> Trie a -> b++-- | The default definition as of base-4.16.0.0.+foldl_default f z t =+ appEndo (getDual (foldMap_v027 (Dual . Endo . flip f) t)) z++-- | Variant of the default that uses @('#.')@ in lieu of @('.')@.+-- This one performs massively better than 'foldl_default'.+-- TODO: send a patch to @base@ for this.+-- Hrm, for larger tries it looks like the benefit is only minimal...+foldl_default_Coerce f z t =+ appEndo (getDual (foldMap_v027 (Dual #. Endo #. flip f) t)) z++-- | bytestring-trie-0.2.7 definition, also used for defaults.+-- This one is faster than 'foldl_default_Coerce'; though it allocates+-- ~56% more than 'foldl_default_Coerce' (albeit ~11% /less/ than+-- 'foldl_default').+foldl_v027 f z0 = go z0 -- eta for better inlining+ where+ go z Empty = z+ go z (Arc _ Nothing t) = go z t+ go z (Arc _ (Just v) t) = go (f z v) t+ go z (Branch _ _ l r) = go (go z l) r++-- | Since 'foldr_eta' allocates ~43% more than 'foldr_default',+-- yet 'foldl_v027' allocates ~56% more: see if swapping the order+-- of arguments in the recursion changes that.+-- For small tries:+-- * Marginally slower than 'foldl_v027' (<1%).+-- * Allocates ~8% less than 'foldl_v027'; ~19% less than 'foldl_default';+-- * albeit still ~42% more than 'foldl_default_Coerce'.+-- For larger tries: far and away the best of the lot.+foldl_v027_flop f z0 = \t -> go t z0 -- eta for better inlining+ where+ go Empty z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = go t (f z v)+ go (Branch _ _ l r) z = go r (go l z)++-- We could try CPSing to restore the tail-recursion; but since+-- that's worse everywhere else, there seems little point.++----------------------------------------------------------------+foldl'_defaultCompose, foldl'_defaultEta, foldl'_v027, foldl'_v027_flop, foldl'_v027_flop_bang+ :: (b -> a -> b) -> b -> Trie a -> b++-- | The default definition as of base-4.16.0.0 (using 'foldr_compose').+foldl'_defaultCompose f z0 xs =+ foldr_compose f' id xs z0+ where f' x k z = k $! f z x++-- | The default definition as of base-4.16.0.0 (using 'foldr_eta').+-- Much worse than 'foldl'_defaultCompose'.+foldl'_defaultEta f z0 xs =+ foldr_eta f' id xs z0+ where f' x k z = k $! f z x++-- | bytestring-trie-0.2.7 definition, also used for defaults.+-- Clear winner, for small tries. For large tries it's about halfway+-- between the two defaults above, and the two flops below.+foldl'_v027 f z0 = go z0 -- eta for better inlining+ where+ go !z Empty = z+ go z (Arc _ Nothing t) = go z t+ go z (Arc _ (Just v) t) = go (f z v) t+ go z (Branch _ _ l r) = go (go z l) r++-- | For small tries: worse than 'foldl'_v027': ~10% slower, ~119%+-- more allocation. But for large tries it's much faster.+foldl'_v027_flop f z0 = \t -> go t z0 -- eta for better inlining+ where+ go Empty !z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = go t (f z v)+ go (Branch _ _ l r) z = go r (go l z)++-- FIXME: why on earth does this perform better?! Why aren't bang+-- patterns giving me the same thing?!+--+-- | For small tries: ~4.4% faster than 'foldl'_v027'; but allocates+-- ~32% more still. For large tries it's only marginally faster+-- than 'foldl'_v027_flop' (which is to say massively faster than+-- 'foldl'_v027')+foldl'_v027_flop_bang f z0 = \t -> go t z0 -- eta for better inlining+ where+ go Empty !z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = go t $! f z v+ go (Branch _ _ l r) z = go r $! go l z++-- TODO: CPS to restore the purely tail-call for @Branch@?++----------------------------------------------------------------+{-+-- TODO: the base-4.16.0.0 defaults are shown+{-# INLINE toList #-} toList t = build (\ c n -> foldr c n t)+null = foldr (\_ _ -> False) True+length = foldl' (\c _ -> c+1) 0+elem = any . (==)+-- With standalone definition: any p = getAny #. foldMap (Any #. p)++{-# INLINEABLE maximum #-}+maximum = fromMaybe (errorWithoutStackTrace "maximum: empty structure") .+ getMax . foldMap' (Max #. (Just :: a -> Maybe a)) -- -XScopedTypeVariables+{-# INLINEABLE minimum #-}+minimum = fromMaybe (errorWithoutStackTrace "minimum: empty structure") .+ getMin . foldMap' (Min #. (Just :: a -> Maybe a)) -- -XScopedTypeVariables+{-# INLINEABLE sum #-}+sum = getSum #. foldMap' Sum+{-# INLINEABLE product #-}+product = getProduct #. foldMap' Product+-}+++++----------------------------------------------------------------+----------------------------------------------------------------+-- Definitely not the most efficient thing...+-- TODO: define an anamorphism function in "TI" to use here...+arbitraryTrie :: Int -> Int -> QC.Gen (Trie Int)+arbitraryTrie maxK maxL = do+ k <- QC.chooseInt (0, maxK)+ keys <- QC.vectorOf k $ do+ l <- QC.chooseInt (0, maxL)+ xs <- QC.vector l+ return $ S.pack xs+ return . realTrie_to_benchTrie . T.fromList $ zip keys [0..]++-- TODO: still gotta reconstruct the 'Prefix' and 'Mask'.+-- (Even though none of the folds depend on them...)+realTrie_to_benchTrie :: T.Trie a -> Trie a+realTrie_to_benchTrie = TI.cata_ Arc (Branch 0 0) Empty++----------------------------------------------------------------+intToSum :: (Trie (Sum Int) -> a) -> [Trie Int] -> [a]+intToSum f = fmap f . coerce++-- BUG: since we started generating a collection of tries, we've+-- started to see different results: each test progressively slower+-- than the last. Seems bogus, but not sure what's up since criterion+-- ought to be forcing all of @ts@ early and doing GC between each+-- benchmark...+main :: IO ()+main = C.defaultMain+ [ C.env (QC.generate $ QC.vectorOf 10 $ arbitraryTrie 30 10) $ \ ts ->+ bgroup_Foldable ts+ ]++bgroup_Foldable :: [Trie Int] -> C.Benchmark+bgroup_Foldable ts =+ C.bgroup "Foldable"+ [ C.bgroup "fold"+ [ C.bench "default (foldMap)" $ C.nf (intToSum fold_foldMap ) ts+ , C.bench "default (foldr_compose)" $ C.nf (intToSum fold_foldrCompose) ts+ , C.bench "default (foldr_eta)" $ C.nf (intToSum fold_foldrEta ) ts+ , C.bench "v0.2.7" $ C.nf (intToSum fold_v027 ) ts+ ]+ , C.bgroup "foldMap"+ [ C.bench "default (foldr_compose)" $ C.nf (intToSum (foldMap_foldrCompose id)) ts+ , C.bench "default (foldr_eta)" $ C.nf (intToSum (foldMap_foldrEta id)) ts+ , C.bench "v0.2.7" $ C.nf (intToSum (foldMap_v027 id)) ts+ ]+ , C.bgroup "foldMap'"+ [ C.bench "default (foldl')" $ C.nf (intToSum (foldMap'_default id)) ts+ , C.bench "v0.2.7" $ C.nf (intToSum (foldMap'_v027 id)) ts+ , C.bench "v0.2.7 +cps" $ C.nf (intToSum (foldMap'_v027_cps id)) ts+ , C.bench "v0.2.7 +flopped" $ C.nf (intToSum (foldMap'_v027_flop id)) ts+ , C.bench "v0.2.7 +($!)" $ C.nf (intToSum (foldMap'_v027_bang id)) ts+ , C.bench "v0.2.7 +flopped +($!)" $ C.nf (intToSum (foldMap'_v027_flop_bang id)) ts+ ]+ , C.bgroup "foldr"+ [ C.bench "default (foldMap)" $ C.nf (foldr_default (+) 0 <$>) ts+ , C.bench "compose" $ C.nf (foldr_compose (+) 0 <$>) ts+ , C.bench "eta" $ C.nf (foldr_eta (+) 0 <$>) ts+ , C.bench "cps_eta" $ C.nf (foldr_cps_eta (+) 0 <$>) ts+ , C.bench "cps" $ C.nf (foldr_cps (+) 0 <$>) ts+ , C.bench "noClosure" $ C.nf (foldr_noClosure (+) 0 <$>) ts+ ]+ , C.bgroup "foldr'"+ [ C.bench "default" $ C.nf (foldr'_default (+) 0 <$>) ts+ , C.bench "v0.2.7" $ C.nf (foldr'_v027 (+) 0 <$>) ts+ , C.bench "v0.2.7 +cps" $ C.nf (foldr'_v027_cps (+) 0 <$>) ts+ , C.bench "cps, no eta" $ C.nf (foldr'_cps (+) 0 <$>) ts+ , C.bench "v0.2.7 +flopped" $ C.nf (foldr'_v027_flop (+) 0 <$>) ts+ , C.bench "v0.2.7 +($!)" $ C.nf (foldr'_v027_bang (+) 0 <$>) ts+ , C.bench "v0.2.7 +flopped +($!)" $ C.nf (foldr'_v027_flop_bang (+) 0 <$>) ts+ ]+ , C.bgroup "foldl"+ [ C.bench "default (foldMap)" $ C.nf (foldl_default (+) 0 <$>) ts+ , C.bench "default +Coerce" $ C.nf (foldl_default_Coerce (+) 0 <$>) ts+ , C.bench "v0.2.7" $ C.nf (foldl_v027 (+) 0 <$>) ts+ , C.bench "v0.2.7 +flopped" $ C.nf (foldl_v027_flop (+) 0 <$>) ts+ ]+ , C.bgroup "foldl'"+ [ C.bench "default (foldr_compose)" $ C.nf (foldl'_defaultCompose (+) 0 <$>) ts+ , C.bench "default (foldr_eta)" $ C.nf (foldl'_defaultEta (+) 0 <$>) ts+ , C.bench "v0.2.7" $ C.nf (foldl'_v027 (+) 0 <$>) ts+ , C.bench "v0.2.7 +flopped" $ C.nf (foldl'_v027_flop (+) 0 <$>) ts+ , C.bench "v0.2.7 +flopped +($!)" $ C.nf (foldl'_v027_flop_bang (+) 0 <$>) ts+ ]+ ]++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ dev/Bench/MatchOne.hs view
@@ -0,0 +1,124 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP, BangPatterns #-}++----------------------------------------------------------------+-- ~ 2023.03.19+-- |+-- Module : Bench.MatchOne+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : portable+--+-- Benchmarking definitions for 'TI.match_'+----------------------------------------------------------------++module Main (main) where++import qualified Data.Trie as T+import qualified Data.Trie.Internal as TI+import qualified Data.ByteString as S+#if !(MIN_VERSION_base(4,18,0))+-- [base-4.18 / GHC 9.6.1]: Prelude now re-exports 'liftA2'.+import Control.Applicative (liftA2)+#endif+import Control.DeepSeq (NFData)+import qualified Test.QuickCheck as QC+import qualified Criterion.Main as C+-- TODO: consider trying <https://hackage.haskell.org/package/tasty-bench>. Especially so we can add tests to ensure that all these implementations are correct; but also for the comparisons across versions (since @bench-show@ and @benchgraph@ are both too heavy-weight for our needs).+----------------------------------------------------------------++-- Using NOINLINE to improve the reliability\/quality of the benchmarks.+match_v027, match_foldl, match_foldr, match_foldr_alt+ :: T.Trie a -> S.ByteString -> Maybe (Int, a)++-- The manual implementation.+{-# NOINLINE match_v027 #-}+match_v027 = TI.match_++-- This implementation is based on @base-4.16.0.0@:'GHC.List.last'.+-- N.B., the implementation uses 'foldl' in order to be a good+-- consumer, contrary to the 'foldr' we would have expected; see:+-- <https://gitlab.haskell.org/ghc/ghc/-/issues/9339>+-- Also, the actual definition of 'GHC.List.last' must be eta-expanded+-- in order to actually have 'foldl' make it a good consumer; see:+-- <https://gitlab.haskell.org/ghc/ghc/-/issues/10260>+-- (Not that that's relevant for us, since we're inlining the+-- definition anyways, and the call-site is indeed saturated.)+--+-- TODO: since when does 'foldl' make good consumers? Did GHC+-- switch from build\/foldr to unfold\/destroy?+--+-- Per the benchmarks below, this one is by far the slowest; thereby+-- suggesting that 'foldl' is not in fact a good consumer!+{-# NOINLINE match_foldl #-}+match_foldl t q = foldl (\_ x -> Just x) Nothing (TI.matches_ t q)++-- This uses the definition of 'GHC.List.last' prior to #9339. Note+-- how @step@ returns the @Just@ immediately, and only lazily does+-- the case analysis.+--+-- Per the benchmarks below, this is better than 'match_foldl' but+-- not as good as 'match_foldr_alt'+{-# NOINLINE match_foldr #-}+match_foldr t q = foldr step Nothing (TI.matches_ t q)+ where+ step x m = Just (case m of { Nothing -> x; Just y -> y })++-- And here's a version that doesn't do that @Just@-before-case...+--+-- Per the benchmarks below, this is better than 'match_foldr' but+-- still not as good as 'match_v027'.+{-# NOINLINE match_foldr_alt #-}+match_foldr_alt t q = foldr step Nothing (TI.matches_ t q)+ where+ step x Nothing = Just x+ step _ y = y++-- TODO: maybe try a Codensity version of the above two, to avoid+-- redundant case analysis on the intermediate 'Maybe'++----------------------------------------------------------------+-- TODO: move this stuff off to the shared file.+-- TODO: should have argument to bound the 'Word8'+arbitraryBS :: Int -> QC.Gen S.ByteString+arbitraryBS maxL = do+ l <- QC.chooseInt (0, maxL)+ xs <- QC.vector l+ return $! S.pack xs++arbitraryTrie :: Int -> Int -> QC.Gen (T.Trie Int)+arbitraryTrie maxK maxL = do+ k <- QC.chooseInt (0, maxK)+ keys <- QC.vectorOf k $ arbitraryBS maxL+ return $! T.fromList (zip keys [0..])++-- TODO: really need a better environment to work on than this...+generateEnv :: IO ([T.Trie Int], [S.ByteString])+generateEnv = QC.generate $ do+ ts <- QC.vectorOf 10 $ arbitraryTrie 30 10+ qs <- QC.vectorOf 10 $ arbitraryBS 12+ return (ts, qs)++cartesianNF :: NFData c => (a -> b -> c) -> [a] -> [b] -> C.Benchmarkable+cartesianNF f xs ys = C.nf (liftA2 f xs) ys++----------------------------------------------------------------+-- TODO: <https://github.com/haskell/containers/blob/master/containers-tests/benchmarks/IntMap.hs>+-- uses 'Control.Exception.evaluate' instead of 'C.env'. Is that+-- because of using the @gauge@ library instead of @criterion@, or+-- is there some other reason?+main :: IO ()+main = C.defaultMain+ [ C.env generateEnv $ \ ~(ts, qs) ->+ C.bgroup "MatchOne"+ [ C.bench "match_v027" $ cartesianNF match_v027 ts qs+ , C.bench "match_foldl" $ cartesianNF match_foldl ts qs+ , C.bench "match_foldr" $ cartesianNF match_foldr ts qs+ , C.bench "match_foldr_alt" $ cartesianNF match_foldr_alt ts qs+ ]+ ]++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ dev/Bench/Regression.hs view
@@ -0,0 +1,145 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP, BangPatterns #-}++----------------------------------------------------------------+-- ~ 2022.04.03+-- |+-- Module : Bench.Regression+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : portable (with bang-patterns)+--+----------------------------------------------------------------++module Bench.Regression (main) where++import Shared.BaseCompat+import qualified Data.Trie as T+-- import qualified Data.Trie.Internal as TI+import qualified Data.ByteString as S++#if MIN_VERSION_base(4,7,0)+-- [GHC 7.8.1]: "Data.Coerce" added to base.+import Data.Coerce (Coercible, coerce)+#endif+import qualified Data.Foldable as F+import qualified Criterion.Main as C+import qualified System.IO as Sys (withFile, IOMode(..), hIsEOF)+import qualified System.Environment as Sys (getProgName, getArgs, withArgs)+import qualified System.Exit as Sys (exitFailure)+----------------------------------------------------------------++-- | Read a file and insert each line into a trie with its (base-0)+-- line number.+readTrieFromFile :: FilePath -> IO (T.Trie Int)+readTrieFromFile file = Sys.withFile file Sys.ReadMode (go 0 T.empty)+ where+ go !i !t !h = do+ b <- Sys.hIsEOF h+ if b+ then return t+ else do+ line <- S.hGetLine h+ go (i+1) (T.insert line i t) h++-- TODO: maybe add some more CLI structure, for things like choosing+-- to use random inputs instead of file, or parameters of randomness,+-- etc.+main :: IO ()+main = do+ args <- Sys.getArgs+ case args of+ [] -> do+ prog <- Sys.getProgName+ putStrLn "ERROR: Missing file argument"+ putStrLn $ "Usage: " ++ prog ++ " FILE [criterionFlags]"+ Sys.exitFailure+ file:rest ->+ Sys.withArgs rest $ C.defaultMain+ [ C.env (readTrieFromFile file) $ \ t ->+ bgroup_Foldable t+ -- TODO: more benchmark suites here.+ ]++----------------------------------------------------------------+----------------------------------------------------------------+type BenchmarkE env = env -> C.Benchmark+type BenchmarkableE env = env -> C.Benchmarkable++bgroupE :: String -> [BenchmarkE env] -> BenchmarkE env+bgroupE name bs = \e -> C.bgroup name (($ e) <$> bs)+{-# INLINE bgroupE #-}++benchE :: String -> BenchmarkableE env -> BenchmarkE env+benchE name b = \e -> C.bench name (b e)+{-# INLINE benchE #-}++----------------------------------------------------------------+#if MIN_VERSION_base(4,7,0)+{-+-- | From "Data.Functor.Utils", but isn't exported. Is used heavily+-- by the default implementations, since they use so many newtype+-- wrappers.+(#.) :: Coercible b c => (b -> c) -> (a -> b) -> (a -> c)+(#.) _ = coerce+{-# INLINE (#.) #-}+-}++-- My own variant, ignoring the second argument instead.+(.#) :: Coercible a b => (b -> c) -> (a -> b) -> (a -> c)+(.#) f _ = coerce f+{-# INLINE (.#) #-}+#endif++----------------------------------------------------------------+bgroup_Foldable :: BenchmarkE (T.Trie Int)+bgroup_Foldable =+ bgroupE "Foldable"+ -- BUG: For these two 'fold' tests, whichever one we put first+ -- runs much more slowly than the one we put second! Thus, there's+ -- some sort of bug in how 'C.env' is forcing things. And adding+ -- calls to 'Control.Exception.evaluate' and 'Control.DeepSeq.rnf'+ -- doesn't help any. So for now we just put a nonce test first+ -- to do whatever forcing is necessary.+ [ benchE "IGNORE_THIS" $ C.nf (F.fold . fmap Sum)+ -- Before adding rewrite rules for the 'Functor' type class, the+ -- test using @(.)@ used to run monstrously slower than the version+ -- using @(.#)@; indicating that those rewrites didn't automatically+ -- come for free. Since defining our own rewrite rules, the two+ -- versions seem about the same and it varies which one comes out+ -- faster. Marking 'fold' as INLINE (instead of INLINABLE) causes+ -- the @(.)@ variant to be closer to 'foldMap' than to the+ -- 'fold' @(.#)@ variant.+ , benchE "fold(.)" $ C.nf (F.fold . fmap Sum)+#if MIN_VERSION_base(4,7,0)+ , benchE "fold(.#)" $ C.nf (F.fold .# fmap Sum)+#endif+ , benchE "foldMap" $ C.nf (F.foldMap Sum)+#if MIN_VERSION_base(4,13,0)+ , benchE "foldMap'" $ C.nf (F.foldMap' Sum)+#endif+-- (2021.03.12): according to this benchmark, our hand-rolled 'foldr'+-- is indeed monstrously faster than the default implementation.+ , benchE "foldr" $ C.nf (F.foldr (+) 0)+#if MIN_VERSION_base(4,6,0)+ , benchE "foldr'" $ C.nf (F.foldr' (+) 0)+#endif+ , benchE "foldl" $ C.nf (F.foldl (+) 0)+#if MIN_VERSION_base(4,6,0)+ , benchE "foldl'" $ C.nf (F.foldl' (+) 0)+#endif+#if MIN_VERSION_base(4,8,0)+ , benchE "length" $ C.nf F.length+ {-+ , benchE "maximum" $ C.nf F.maximum -- Must first ensure non-empty!+ , benchE "minimum" $ C.nf F.minimum -- Must first ensure non-empty!+ -}+#else+ , benchE "size" $ C.nf T.size+#endif+ ]++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ dev/Bench/UnionWith.hs view
@@ -0,0 +1,70 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP, BangPatterns #-}++----------------------------------------------------------------+-- ~ 2023.03.19+-- |+-- Module : Bench.UnionWith+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : portable+--+-- Benchmarking the benefits of 'TI.wip_unionWith' vs 'TC.unionWith'.+----------------------------------------------------------------++module Main (main) where++import qualified Data.Trie as T+import qualified Data.Trie.Internal as TI+import qualified Data.Trie.Convenience as TC+import qualified Data.ByteString as S+#if !(MIN_VERSION_base(4,18,0))+-- [base-4.18 / GHC 9.6.1]: Prelude now re-exports 'liftA2'.+import Control.Applicative (liftA2)+#endif+import Control.DeepSeq (NFData)+import qualified Test.QuickCheck as QC+import qualified Criterion.Main as C+----------------------------------------------------------------+++----------------------------------------------------------------+-- TODO: move this stuff off to the shared file.+-- TODO: should have argument to bound the 'Word8'+arbitraryBS :: Int -> QC.Gen S.ByteString+arbitraryBS maxL = do+ l <- QC.chooseInt (0, maxL)+ xs <- QC.vector l+ return $! S.pack xs++arbitraryTrie :: Int -> Int -> QC.Gen (T.Trie Int)+arbitraryTrie maxK maxL = do+ k <- QC.chooseInt (0, maxK)+ keys <- QC.vectorOf k $ arbitraryBS maxL+ return $! T.fromList (zip keys [0..])++-- TODO: really need a better environment to work on than this...+generateEnv :: IO [T.Trie Int]+generateEnv = QC.generate $ QC.vectorOf 10 $ arbitraryTrie 30 10++cartesianNF :: NFData c => (a -> b -> c) -> [a] -> [b] -> C.Benchmarkable+cartesianNF f xs ys = C.nf (liftA2 f xs) ys++----------------------------------------------------------------+-- TODO: <https://github.com/haskell/containers/blob/master/containers-tests/benchmarks/IntMap.hs>+-- uses 'Control.Exception.evaluate' instead of 'C.env'. Is that+-- because of using the @gauge@ library instead of @criterion@, or+-- is there some other reason?+main :: IO ()+main = C.defaultMain+ [ C.env generateEnv $ \ ts ->+ C.bgroup "UnionWith"+ [ C.bench "TI.wip_unionWith" $ cartesianNF (TI.wip_unionWith (+)) ts ts+ , C.bench "TC.unionWith" $ cartesianNF (TC.unionWith (+)) ts ts+ ]+ ]++----------------------------------------------------------------+----------------------------------------------------------- fin.
@@ -0,0 +1,200 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP #-}++----------------------------------------------------------------+-- ~ 2022.03.12+-- |+-- Module : Shared.BaseCompat+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : portable (CPP)+--+-- Compatibility shim for various versions of @base@.+--+-- TODO: depending how much more we need in here, consider instead+-- just using the @base-compat-batteries@ package instead.+----------------------------------------------------------------+module Shared.BaseCompat+ (+ -- * "Data.Semigroup"+ (<>)+ , Sum(..)+ , Product(..)+ -- TODO: (?) 'Min', 'Max', 'Endo', 'Data.Monoid.Dual'+ -- * "Data.Functor"+ , (<&>)+ -- * "Data.Functor.Compose"+ , Compose(..)+ -- * "Data.Functor.Identity"+ , Identity(..)+ ) where++----------------------------------------------------------------+#if MIN_VERSION_base(4,13,0)+-- [GHC 8.8.1]: @(<&>)@ added to "Data.Functor".+import Data.Functor ((<&>))+#endif++----------------------------------------------------------------+#if MIN_VERSION_base(4,13,0)+-- [GHC 8.8.1]: Prelude re-exports 'Semigroup' and @('<>')@, but not 'stimes'.+#elif MIN_VERSION_base(4,9,0)+-- [GHC 8.0.1]: "Data.Semigroup" added to base.+import Data.Semigroup (Semigroup((<>)))+#elif MIN_VERSION_base(4,5,0)+-- [GHC 7.4.1]: @(<>)@ added to "Data.Monoid".+import Data.Monoid ((<>))+#endif++----------------------------------------------------------------+-- For [base 4.13 / GHC 8.8.1], this module needs to explicitly+-- import 'Sum' in order to re-export it. However, for some reason+-- those versions will also cause users of this module to get an+-- \"unused import\" warning, even though the Prelude doesn't export+-- 'Data.Semigroup.Sum'.+-- TODO: figure out what the heck is going on re that warning...+-- FIXME: now getting that issue for GHC 8.0--8.6 too...+#if MIN_VERSION_base(4,9,0)+-- [GHC 8.0.1]+import Data.Semigroup (Sum(..), Product(..))+import Data.Functor.Compose (Compose(..))+#endif++#if MIN_VERSION_base(4,8,0)+-- [GHC 7.10.1]+import Data.Functor.Identity (Identity(..))+#endif++#if MIN_VERSION_base(4,7,0) && !(MIN_VERSION_base(4,9,0))+-- [GHC 7.8.1]: "Data.Coerce" added to base.+import Data.Coerce (coerce)+#endif++----------------------------------------------------------------+----------------------------------------------------------------+#if !(MIN_VERSION_base(4,13,0))+(<&>) :: Functor f => f a -> (a -> b) -> f b+(<&>) = flip fmap+{-# INLINE (<&>) #-}+#endif++----------------------------------------------------------------+#if !(MIN_VERSION_base(4,9,0))+-- | Same as the derived instance, if the type was declared without+-- the record\/field syntax.+newtypeShowsPrec :: Show a => String -> (b -> a) -> Int -> b -> ShowS+newtypeShowsPrec name oldtype d x =+ showParen (d > 10)+ $ showString name+ . showChar ' '+ . showsPrec 11 (oldtype x)+-- aka:+-- > newtypeShowsPrec name oldtype =+-- > Data.Functor.Classes.showsUnaryWith (\d -> showsPrec d . oldtype) name+-- But that function was also introduced in base-4.9.0.0+#endif++----------------------------------------------------------------+#if !(MIN_VERSION_base(4,9,0))+newtype Sum a = Sum { getSum :: a }+ deriving (Eq, Ord, Bounded, Num)+ -- We're not deriving the 'Show' instance, even though base does.+ -- Also forgoing the 'Read' instance.++instance (Show a) => Show (Sum a) where+ showsPrec = newtypeShowsPrec "Sum" getSum++instance Num a => Monoid (Sum a) where+ mempty = Sum 0+# if MIN_VERSION_base(4,7,0)+ mappend = coerce ((+) :: a -> a -> a)+# else+ mappend (Sum x) (Sum y) = Sum (x + y)+# endif++-- TODO: if needed, there are also instances for: Functor, Applicative, Monad.+#endif++----------------------------------------------------------------+#if !(MIN_VERSION_base(4,9,0))+newtype Product a = Product { getProduct :: a }+ deriving (Eq, Ord, Bounded, Num)+ -- We're not deriving the 'Show' instances, even though base does.+ -- Also forgoing the 'Read' instance.++instance (Show a) => Show (Product a) where+ showsPrec = newtypeShowsPrec "Product" getProduct++instance Num a => Monoid (Product a) where+ mempty = Product 1+# if MIN_VERSION_base(4,7,0)+ mappend = coerce ((*) :: a -> a -> a)+# else+ mappend (Product x) (Product y) = Product (x * y)+# endif++-- TODO: if needed, there are also instances for: Functor, Applicative, Monad.+#endif++----------------------------------------------------------------+#if !(MIN_VERSION_base(4,9,0))+newtype Compose f g a = Compose { getCompose :: f (g a) }+ -- Ignoring the other instances since we don't need them.++-- BUG: can't define 'Show' instance without 'Show1' for @f,g@; but+-- 'Show1' wasn't introduced until base-4.9.0.0. Ditto for 'Eq',+-- 'Ord'.++instance (Functor f, Functor g) => Functor (Compose f g) where+ fmap f (Compose x) = Compose (fmap (fmap f) x)+# if __GLASGOW_HASKELL__+ a <$ Compose x = Compose (fmap (a <$) x)+# endif++instance (Foldable f, Foldable g) => Foldable (Compose f g) where+ foldMap f (Compose t) = foldMap (foldMap f) t++instance (Traversable f, Traversable g) => Traversable (Compose f g) where+ traverse f (Compose t) = Compose <$> traverse (traverse f) t++instance (Applicative f, Applicative g) => Applicative (Compose f g) where+ pure x = Compose (pure (pure x))+ Compose f <*> Compose x = Compose (liftA2 (<*>) f x)+#endif++----------------------------------------------------------------+#if !(MIN_VERSION_base(4,8,0))+newtype Identity a = Identity { runIdentity :: a }+ deriving (Eq, Ord, Bounded, Num)+ -- Ignoring the other instances since we don't need them.++instance (Show a) => Show (Identity a) where+ showsPrec = newtypeShowsPrec "Identity" runIdentity++instance Functor Identity where+# if MIN_VERSION_base(4,7,0)+ fmap = coerce+# else+ fmap f (Identity x) = Identity (f x)+# endif++instance Applicative Identity where+ pure = Identity+# if MIN_VERSION_base(4,7,0)+ (<*>) = coerce+# else+ Identity f <*> Identity x = Identity (f x)+# endif++-- Using 'runIdentity' here instead of pattern matching, because+-- that's what base-4.16.0.0 does.+instance Monad Identity where+ m >>= k = k (runIdentity m)++-- TODO: if needed, there's also a Foldable instance.+#endif++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ dev/Test/Main.hs view
@@ -0,0 +1,197 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP+ , MultiParamTypeClasses+ , FlexibleContexts+ #-}++----------------------------------------------------------------+-- ~ 2022.03.05+-- |+-- Module : Test.Main+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : semi-portable (MPTC,...)+--+-- Testing 'Trie's.+----------------------------------------------------------------+module Main (main) where++#if !(MIN_VERSION_base(4,11,0))+-- [GHC 8.4.1]: This guard is just to avoid an \"unused import\" warning.+import Shared.BaseCompat+#endif+import Test.Utils+import Test.Properties (smallcheckTests, quickcheckTests)++import qualified Data.Trie as T+import qualified Data.ByteString as S++import qualified System.Exit as System (exitSuccess, exitFailure)+import qualified System.IO as System (hPutStrLn, stderr)+import qualified Test.Tasty.Ingredients as Tasty (tryIngredients)+import qualified Test.Tasty.Options as Tasty (singleOption, OptionSet)+import qualified Test.Tasty.Runners as Tasty (installSignalHandlers, parseOptions)+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.SmallCheck as SC+import qualified Test.Tasty.QuickCheck as QC++----------------------------------------------------------------+----------------------------------------------------------------+-- We can't use 'Tasty.defaultMain' together with 'Tasty.localOption',+-- because what we want to do is to set new defaults but still allow+-- the commandline to override those defaults, and I don't see any+-- way to do that with 'Tasty.defaultMain'.+--+-- TODO: Still need some way to remove the few remaining+-- 'Tasty.localOption' calls that aren't global...+main :: IO ()+main = do+ let ins = Tasty.defaultIngredients+ Tasty.installSignalHandlers+ opts <- Tasty.parseOptions ins tests+ case Tasty.tryIngredients ins (globalOptions <> opts) tests of+ Nothing -> do+ System.hPutStrLn System.stderr+ "No ingredients agreed to run. Something is wrong."+ System.exitFailure+ Just act -> do+ ok <- act+ if ok+ then System.exitSuccess+ else System.exitFailure++-- We add the timeout for the sake of GithubActions CI, so we don't+-- accidentally blow our budget. So far, even with HPC enabled,+-- each test takes less than 1sec (almost all of them taking less+-- than 0.5sec); so allowing 10s per test is more than generous.+-- Since the full suite takes around 20sec, that means the timeout+-- will cap the suite to take no more than 200sec / 3.3min.+globalOptions :: Tasty.OptionSet+globalOptions = mconcat+ [ Tasty.singleOption (Tasty.mkTimeout 10000000) -- 10sec; in microsecs+ , Tasty.singleOption (QC.QuickCheckTests 500) -- QC.Args.maxSuccess+ , Tasty.singleOption (QC.QuickCheckMaxSize 400) -- QC.Args.maxSize+ , Tasty.singleOption (QC.QuickCheckMaxRatio 10) -- QC.Args.maxDiscardRatio+ , Tasty.singleOption (SC.SmallCheckDepth 3)+ ]++-- We run the HUnit and smallcheck tests first, since they're the+-- fastest. Thus, we only pay for the QuickCheck tests if we have to.+tests :: Tasty.TestTree+tests =+ Tasty.testGroup "All Tests"+ [ hunitTests+ , Tasty.testGroup "Properties"+ [ smallcheckTests+ , quickcheckTests+ ]+ ]++hunitTests :: Tasty.TestTree+hunitTests =+ Tasty.testGroup "HUnit"+ [ test_Union+ , test_Intersect+ , test_Submap+ , test_Insert+ , test_Delete+ ]+++----------------------------------------------------------------+----------------------------------------------------------------+-- Because we avoid epsilons everywhere else, need to make sure+-- 'mergeBy' gets it right+test_Union :: Tasty.TestTree+test_Union =+ Tasty.testGroup "mergeBy"+ [ testEqual "unionL epsilon" (e1 `T.unionL` e2) e1+ , testEqual "unionR epsilon" (e1 `T.unionR` e2) e2+ , testEqual "unionL regression" (tLeft `T.unionL` tRight) tLeftResult+ , testEqual "unionR regression" (tLeft `T.unionR` tRight) tRightResult+ ]+ where+ e1 = T.singleton S.empty (4::Int)+ e2 = T.singleton S.empty (2::Int)++ -- Regression test against bug filed by Gregory Crosswhite on+ -- 2010.06.10 against version 0.2.1.1.+ a, b :: S.ByteString+ a = read "\"\231^\179\160Y\134Gr\158<)&\222\217#\156\""+ b = read "\"\172\193\GSp\222\174GE\186\151\DC1#P\213\147\SI\""+ tLeft, tRight, tRightResult, tLeftResult :: T.Trie Int+ tLeft = T.fromList [(a,1), (b,0)]+ tRight = T.fromList [(a,2)]+ tRightResult = T.fromList [(a,2), (b,0)]+ tLeftResult = T.fromList [(a,1), (b,0)]++test_Intersect :: Tasty.TestTree+test_Intersect =+ Tasty.testGroup "intersectBy"+ [ testEqual "intersectL" (tLeft `T.intersectL` tRight) tLeftResult+ , testEqual "intersectR" (tLeft `T.intersectR` tRight) tRightResult+ ]+ where+ -- Trivial regression example+ a, b, c :: S.ByteString+ a = packC2W "a"+ b = packC2W "b"+ c = packC2W "c"+ tLeft, tRight, tRightResult, tLeftResult :: T.Trie Int+ tLeft = T.fromList [(S.empty,0), (a,1), (b,3)]+ tRight = T.fromList [ (a,2), (c,4)]+ tLeftResult = T.fromList [(a,1)]+ tRightResult = T.fromList [(a,2)]+ -- TODO: better unit test for when one string is prefix of another.++----------------------------------------------------------------+test_Submap :: Tasty.TestTree+test_Submap =+ Tasty.testGroup "submap"+ [ nullSubmap "split on arc fails" fi True+ , nullSubmap "prefix of arc matches" fo False+ , nullSubmap "suffix of empty fails" food True+ , nullSubmap "missing branch fails" bag True+ , nullSubmap "at a branch matches" ba False+ ]+ where+ t = vocab2trie ["foo", "bar", "baz"]+ fi = packC2W "fi"+ fo = packC2W "fo"+ food = packC2W "food"+ ba = packC2W "ba"+ bag = packC2W "bag"++ nullSubmap s q b = testEqual s (T.null $ T.submap q t) b++----------------------------------------------------------------+-- requires Eq (Trie a) and, in case it fails, Show (Trie a)+test_Insert :: Tasty.TestTree+test_Insert =+ Tasty.testGroup "insert"+ [ testEqual "insertion is commutative for prefix/superfix"+ (T.insert aba o $ T.insert abaissed i $ T.empty)+ (T.insert abaissed i $ T.insert aba o $ T.empty)+ ]+ where+ aba = packC2W "aba"+ abaissed = packC2W "abaissed"++ o = 0::Int+ i = 1::Int+++test_Delete :: Tasty.TestTree+test_Delete =+ Tasty.testGroup "delete"+ [ testEqual "deleting epsilon from empty trie is empty"+ (T.delete epsilon T.empty) (T.empty :: T.Trie Int)+ ]+ where+ -- TODO: why not 'S.empty'?+ epsilon = packC2W ""++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ dev/Test/Properties.hs view
@@ -0,0 +1,1245 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP+ , MultiParamTypeClasses+ , FlexibleContexts+ #-}++-- HACK: Enable this to locally disable -Wunused-top-binds for some+-- HOF properties that I haven't figured out how to decently test+-- yet. This CPP hack allows to quickly "uncomment" all such code,+-- to make sure it still typechecks et al.+#define HideUnusedTopBonds++----------------------------------------------------------------+-- ~ 2023.03.19+-- |+-- Module : Test.Properties+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : semi-portable (MPTC,...)+--+-- Property testing 'Trie's.+----------------------------------------------------------------+module Test.Properties (smallcheckTests, quickcheckTests) where++import Shared.BaseCompat+import Test.Utils++import qualified Data.Trie as T+import qualified Data.Trie.Internal as TI+import qualified Data.Trie.Convenience as TC+import qualified Data.ByteString as S++import qualified Test.Tasty as Tasty+import qualified Test.Tasty.SmallCheck as SC+import qualified Test.Tasty.QuickCheck as QC++#if !(MIN_VERSION_base(4,18,0))+-- [base-4.18 / GHC 9.6.1]: Prelude now re-exports 'liftA2'.+import Control.Applicative (liftA2)+#endif+#ifndef HideUnusedTopBonds+import Control.Monad (join, (<=<))+#endif+import Control.Monad (guard)+import Data.List (nubBy, sortBy)+import Data.Maybe (fromJust, isJust)+import Data.Ord (comparing)++import qualified Data.Binary as B+import qualified Data.Foldable as F++-- Repeated from "Shared.BaseCompat" because we need the class here.+#if MIN_VERSION_base(4,13,0)+-- [aka GHC 8.8.1]: Prelude re-exports 'Semigroup'.+#elif MIN_VERSION_base(4,9,0)+-- [aka GHC 8.0.1]: "Data.Semigroup" added to base.+import Data.Semigroup (Semigroup)+#endif++#if MIN_VERSION_base(4,9,0)+import Data.Functor.Classes (Eq1)+#endif++----------------------------------------------------------------+----------------------------------------------------------------+{-+TODO: see if we can't figure out some way of wrapping our properties+so that we can just write this list once and then pass in a token+for which checker to resolve to; something like:++ data Prop a = Prop String a++ data PropChecker = QuickCheck | SmallCheck+ deriving (Eq, Show)++ testProp :: (QC.Testable a, SC.Testable IO a) => PropChecker -> Prop a -> Tasty.TestTree+ testProp QuickCheck (Prop name a) = QC.testProperty name a+ testProp SmallCheck (Prop name a) = SC.testProperty name a++Of course, the problem with that implementation is that we need to+have the Prop remain polymorphic in the CheckGuard type, and have+testProp resolve it depending on the PropChecker. So is there a way+to do that without GADTs or impredicativity?+-}++quickcheckTests :: Tasty.TestTree+quickcheckTests+ = Tasty.testGroup "QuickCheck"+ [ Tasty.testGroup "Data.Trie.ByteStringInternal"+ [ QC.testProperty+ "prop_breakMaximalPrefix"+ (prop_breakMaximalPrefix :: WS -> WS -> Bool)+ ]+ , Tasty.testGroup "Trivialities (@Int)"+ [ QC.testProperty+ "prop_singleton"+ (prop_singleton :: WS -> Int -> Bool)+ , QC.testProperty+ "prop_insert_lookup"+ (prop_insert_lookup :: WS -> Int -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_delete_lookup"+ (prop_delete_lookup :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_insert_size"+ (prop_insert_size :: WS -> Int -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_delete_size"+ (prop_delete_size :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_insert_delete"+ (prop_insert_delete :: WS -> Int -> WTrie Int -> Bool)+ ]+ , Tasty.testGroup "Submap (@Int)"+ [ QC.testProperty+ "prop_submap_keysAreMembers"+ (prop_submap_keysAreMembers :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_submap_keysHavePrefix"+ (prop_submap_keysHavePrefix :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_submap_valuesEq"+ (prop_submap_valuesEq :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_deleteSubmap_keysAreMembers"+ (prop_deleteSubmap_keysAreMembers :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_deleteSubmap_keysLackPrefix"+ (prop_deleteSubmap_keysLackPrefix :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_deleteSubmap_disunion"+ (prop_deleteSubmap_disunion :: WS -> WTrie Int -> Bool)+ ]+ , Tasty.localOption (QC.QuickCheckMaxSize 300)+ -- BUG: fix that 'Tasty.localOption'+ $ Tasty.testGroup "unionWith (@Int)"+ [ QC.testProperty+ "prop_unionL"+ (prop_unionL :: WTrie Int -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_unionR"+ (prop_unionR :: WTrie Int -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_unionPlus"+ (prop_unionPlus :: WTrie Int -> WTrie Int -> Bool)+ ]+ , Tasty.localOption (QC.QuickCheckMaxSize 300)+ -- BUG: fix that 'Tasty.localOption'+ $ Tasty.testGroup "intersectBy (@Int)"+ [ QC.testProperty+ "prop_intersectL"+ (prop_intersectL :: WTrie Int -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_intersectR"+ (prop_intersectR :: WTrie Int -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_intersectPlus"+ (prop_intersectPlus :: WTrie Int -> WTrie Int -> Bool)+ ]+ , Tasty.testGroup "Matching (@Int)"+ [ QC.testProperty+ "prop_matches_keysOrdered"+ (prop_matches_keysOrdered :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_matches_keysArePrefix"+ (prop_matches_keysArePrefix :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_minMatch_is_first_matches"+ (prop_minMatch_is_first_matches :: WS -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_match_is_last_matches"+ (prop_match_is_last_matches :: WS -> WTrie Int -> Bool)+ ]+ , Tasty.testGroup "Priority-queue (@Int)"+ [ QC.testProperty+ "prop_minAssoc_is_first_toList"+ (prop_minAssoc_is_first_toList :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_maxAssoc_is_last_toList"+ (prop_maxAssoc_is_last_toList :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_updateMinViewBy_ident"+ (prop_updateMinViewBy_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_updateMaxViewBy_ident"+ (prop_updateMaxViewBy_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_updateMinViewBy_gives_minAssoc"+ (prop_updateMinViewBy_gives_minAssoc :: (WS -> Int -> Maybe Int) -> WTrie Int -> Bool)+ , QC.testProperty+ "prop_updateMaxViewBy_gives_maxAssoc"+ (prop_updateMaxViewBy_gives_maxAssoc :: (WS -> Int -> Maybe Int) -> WTrie Int -> Bool)+ ]+ , Tasty.testGroup "toList (@Int)"+ [ QC.testProperty+ "prop_toListBy_keysOrdered"+ (prop_toListBy_keysOrdered :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_keys"+ (prop_keys :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_elems"+ (prop_elems :: WTrie Int -> Bool)+ ]+ , Tasty.testGroup "fromList (@Int)"+ [ QC.testProperty+ "prop_fromList_takes_first"+ (prop_fromList_takes_first :: [(WS, Int)] -> Bool)+ , QC.testProperty+ "prop_fromListR_takes_first"+ (prop_fromListR_takes_first :: [(WS, Int)] -> Bool)+ , QC.testProperty+ "prop_fromListL_takes_first"+ (prop_fromListL_takes_first :: [(WS, Int)] -> Bool)+ , QC.testProperty+ "prop_fromListS_takes_first"+ (prop_fromListS_takes_first :: [(WS, Int)] -> Bool)+ , QC.testProperty+ "prop_fromListWithConst_takes_first"+ (prop_fromListWithConst_takes_first :: [(WS, Int)] -> Bool)+ , QC.testProperty+ "prop_fromListWithLConst_takes_first"+ (prop_fromListWithLConst_takes_first :: [(WS, Int)] -> Bool)+ ]+ , Tasty.testGroup "Type classes"+ [ Tasty.testGroup "Functor (@Int)"+ [ QC.testProperty+ "prop_FunctorIdentity"+ (prop_FunctorIdentity :: WTrie Int -> Bool)+ -- TODO: prop_FunctorCompose+ {-+ -- TODO: still worth it to do this test with 'undefined'?+ , QC.testProperty+ "prop_fmap_keys"+ (prop_fmap_keys (undefined :: Int -> Int) :: WTrie Int -> Bool)+ -- TODO: prop_fmap_elems+ -}+#if __GLASGOW_HASKELL__+ , QC.testProperty+ "prop_fmapConst"+ (prop_fmapConst :: Int -> WTrie Int -> Bool)+#endif+ , QC.testProperty+ -- TODO: generalize to other functions.+ "prop_fmap_toList"+ (prop_fmap_toList (+1) :: WTrie Int -> Bool)+ ]+ , Tasty.testGroup "Applicative (@Int)"+ [ QC.testProperty+ "prop_ApplicativeIdentity"+ (prop_ApplicativeIdentity :: WTrie Int -> Bool)+ -- TODO: prop_ApplicativeCompose, prop_ApplicativeHom, prop_ApplicativeInterchange+ ]+ , Tasty.testGroup "Monad (@Int)"+ [ QC.testProperty+ "prop_MonadIdentityR"+ (prop_MonadIdentityR :: WTrie Int -> Bool)+ -- TODO: prop_MonadIdentityL, prop_MonadAssoc+ ]+ , Tasty.testGroup "Foldable (@Int/Sum Int)"+ [ QC.testProperty+ "prop_foldmapIdentity"+ (prop_foldmapIdentity :: WTrie (Sum Int) -> Bool)+ , QC.testProperty+ "prop_foldmapFusion"+ (prop_foldmapFusion Sum :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_length"+ (prop_length :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_foldr_vs_foldrWithKey"+ (prop_foldr_vs_foldrWithKey :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_foldr'_vs_foldrWithKey'"+ (prop_foldr'_vs_foldrWithKey' :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_foldl_vs_foldlWithKey"+ (prop_foldl_vs_foldlWithKey :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_foldl'_vs_foldlWithKey'"+ (prop_foldl'_vs_foldlWithKey' :: WTrie Int -> Bool)+#if MIN_VERSION_base(4,6,0)+ , QC.testProperty+ "prop_foldr_vs_foldr'"+ (prop_foldr_vs_foldr' :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_foldl_vs_foldl'"+ (prop_foldl_vs_foldl' :: WTrie Int -> Bool)+#endif+#if MIN_VERSION_base(4,13,0)+ , QC.testProperty+ "prop_foldMap_vs_foldMap'"+ (prop_foldMap_vs_foldMap' :: WTrie Int -> Bool)+#endif+ ]+ , Tasty.testGroup "Traversable (@Int)"+ [ QC.testProperty+ "prop_TraverseIdentity"+ (prop_TraverseIdentity :: WTrie Int -> Bool)+ -- TODO: prop_TraverseNaturality, prop_TraverseComposition+ , QC.testProperty+ "prop_SequenceaIdentity"+ (prop_SequenceaIdentity :: WTrie Int -> Bool)+ -- TODO: prop_SequenceaNaturality, prop_SequenceaComposition+ ]+ , Tasty.testGroup "Filterable (@Int)"+ [ QC.testProperty+ "prop_FiltermapFission"+ (prop_FiltermapFission remTriple :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_FiltermapFusion"+ (prop_FiltermapFusion (`rem` 3) (not . isTriple) :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_FiltermapConservation"+ (prop_FiltermapConservation (+1) :: WTrie Int -> Bool)+ -- TODO: prop_FiltermapVertComposition+ , QC.testProperty+ "prop_FilterDefinition"+ (prop_FilterDefinition isTriple :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_FilterVertComposition"+ (prop_FilterVertComposition even isTriple :: WTrie Int -> Bool)+ ]+ , Tasty.testGroup "Witherable (@Int)"+ [ QC.testProperty+ "prop_WitherConservation"+ (prop_WitherConservation (Identity . (+1)) :: WTrie Int -> Bool)+ -- TODO: prop_WitherNaturality, prop_WitherPurity, prop_WitherHorizComposition+ , QC.testProperty+ "prop_FilteraDefinition"+ (prop_FilteraDefinition (Identity . even) :: WTrie Int -> Bool)+ -- TODO: prop_FilteraNaturality, prop_FilteraPurity, prop_FilteraHorizComposition, prop_FilteraHorizComposition_alt+ ]+#if MIN_VERSION_base(4,9,0)+ , Tasty.testGroup "Semigroup (@Sum Int)"+ [ QC.testProperty+ -- This one is a bit more expensive: 1~2sec instead of <=0.5sec+ -- TODO: see if we can't fix that without ruining the utility of the test.+ "prop_Semigroup"+ (prop_Semigroup :: WTrie (Sum Int) -> WTrie (Sum Int) -> WTrie (Sum Int) -> Bool)+ ]+#endif+ , Tasty.testGroup "Monoid (@Sum Int)"+ [ QC.testProperty+ "prop_MonoidIdentityL"+ (prop_MonoidIdentityL :: WTrie (Sum Int) -> Bool)+ , QC.testProperty+ "prop_MonoidIdentityR"+ (prop_MonoidIdentityR :: WTrie (Sum Int) -> Bool)+ ]+ , Tasty.testGroup "Binary (@Int)"+ [ QC.testProperty+ "prop_Binary"+ (prop_Binary :: WTrie Int -> Bool)+ ]+ ]+ , Tasty.testGroup "Other mapping/filtering (@Int)"+ [ QC.testProperty+ "prop_filterMap_ident"+ (prop_filterMap_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_filterMap_empty"+ (prop_filterMap_empty :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_mapBy_keys"+ (prop_mapBy_keys :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_contextualMap_ident"+ (prop_contextualMap_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_contextualMap'_ident"+ (prop_contextualMap'_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_contextualFilterMap_ident"+ (prop_contextualFilterMap_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_contextualMapBy_keys"+ (prop_contextualMapBy_keys :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_contextualMapBy_ident"+ (prop_contextualMapBy_ident :: WTrie Int -> Bool)+ , QC.testProperty+ "prop_contextualMapBy_empty"+ (prop_contextualMapBy_empty :: WTrie Int -> Bool)+ ]+ ]+++----------------------------------------------------------------+adjustSmallCheckDepth+ :: (SC.SmallCheckDepth -> SC.SmallCheckDepth)+ -> Tasty.TestTree -> Tasty.TestTree+adjustSmallCheckDepth = Tasty.adjustOption++-- Throughout, we try to use 'W' or @()@ whenever we can, to reduce+-- the exponential growth problem.+smallcheckTests :: Tasty.TestTree+smallcheckTests+ = Tasty.testGroup "SmallCheck"+ [ Tasty.testGroup "Data.Trie.ByteStringInternal"+ [ adjustSmallCheckDepth (+1)+ $ SC.testProperty+ "prop_breakMaximalPrefix"+ (prop_breakMaximalPrefix :: WS -> WS -> Bool)+ ]+ , Tasty.testGroup "Trivialities (@W/@())"+ [ adjustSmallCheckDepth (+2)+ $ SC.testProperty+ -- This one can easily handle depth=6 fine (~0.1sec), but d=7 (~4sec)+ "prop_singleton"+ (prop_singleton :: WS -> W -> Bool)+ , SC.testProperty+ -- Warning: depth 4 takes >10sec!+ "prop_insert_lookup"+ (prop_insert_lookup :: WS -> W -> WTrie W -> Bool)+ , SC.testProperty+ -- Don't waste any depth on the values!+ -- Warning: depth 4 takes >10sec!+ "prop_delete_lookup"+ (prop_delete_lookup :: WS -> WTrie () -> Bool)+ , SC.testProperty+ -- Don't waste any depth on the values!+ -- Warning: depth 4 takes >10sec!+ "prop_insert_size"+ (prop_insert_size :: WS -> () -> WTrie () -> Bool)+ , SC.testProperty+ -- Don't waste any depth on the values!+ -- Warning: depth 4 takes >10sec!+ -- FIXME: at depth 3: 388/410 (94%) did not meet the condition!+ -- (That is, before rephrasing to avoid using 'CheckGuard')+ "prop_delete_size"+ (prop_delete_size :: WS -> WTrie () -> Bool)+ , SC.testProperty+ -- Note: at depth 3: 136/2120 (6.4%) did not meet the condition.+ -- (That is, before rephrasing to avoid using 'CheckGuard')+ "prop_insert_delete"+ (prop_insert_delete :: WS -> W -> WTrie W -> Bool)+ ]+ , Tasty.testGroup "Submap (@()/@W)"+ -- Depth=4 is at best very marginal here...+ [ SC.testProperty+ "prop_submap_keysAreMembers"+ (prop_submap_keysAreMembers :: WS -> WTrie () -> Bool)+ , SC.testProperty+ "prop_submap_keysHavePrefix"+ (prop_submap_keysHavePrefix :: WS -> WTrie () -> Bool)+ , SC.testProperty+ "prop_submap_valuesEq"+ (prop_submap_valuesEq :: WS -> WTrie W -> Bool)+ , SC.testProperty+ "prop_deleteSubmap_keysAreMembers"+ (prop_deleteSubmap_keysAreMembers :: WS -> WTrie () -> Bool)+ , SC.testProperty+ "prop_deleteSubmap_keysLackPrefix"+ (prop_deleteSubmap_keysLackPrefix :: WS -> WTrie () -> Bool)+ , SC.testProperty+ "prop_deleteSubmap_disunion"+ (prop_deleteSubmap_disunion :: WS -> WTrie W -> Bool)+ ]+ , Tasty.testGroup "unionWith (@W/@Int)"+ -- Warning: Using depth=4 here is bad (the first two take about+ -- 26.43sec; the last one much longer).+ [ SC.testProperty+ "prop_unionL"+ (prop_unionL :: WTrie W -> WTrie W -> Bool)+ , SC.testProperty+ "prop_unionR"+ (prop_unionR :: WTrie W -> WTrie W -> Bool)+ , SC.testProperty+ "prop_unionPlus"+ (prop_unionPlus :: WTrie Int -> WTrie Int -> Bool)+ ]+ , Tasty.testGroup "intersectBy (@W/@Int)"+ -- Warning: Using depth=4 here is bad (the first two take about+ -- 26.43sec; the last one much longer).+ [ SC.testProperty+ "prop_intersectL"+ (prop_intersectL :: WTrie W -> WTrie W -> Bool)+ , SC.testProperty+ "prop_intersectR"+ (prop_intersectR :: WTrie W -> WTrie W -> Bool)+ , SC.testProperty+ "prop_intersectPlus"+ (prop_intersectPlus :: WTrie Int -> WTrie Int -> Bool)+ ]+ , Tasty.testGroup "Matching (@()/@W)"+ [ SC.testProperty+ "prop_matches_keysOrdered"+ (prop_matches_keysOrdered :: WS -> WTrie () -> Bool)+ , SC.testProperty+ "prop_matches_keysArePrefix"+ (prop_matches_keysArePrefix :: WS -> WTrie () -> Bool)+ , SC.testProperty+ "prop_minMatch_is_first_matches"+ (prop_minMatch_is_first_matches :: WS -> WTrie W -> Bool)+ , SC.testProperty+ "prop_match_is_last_matches"+ (prop_match_is_last_matches :: WS -> WTrie W -> Bool)+ ]+ , Tasty.testGroup "Priority-queue (@W)"+ -- Depth=4 takes about 1sec each+ [ SC.testProperty+ "prop_minAssoc_is_first_toList"+ (prop_minAssoc_is_first_toList :: WTrie W -> Bool)+ , SC.testProperty+ "prop_maxAssoc_is_last_toList"+ (prop_maxAssoc_is_last_toList :: WTrie W -> Bool)+ , SC.testProperty+ "prop_updateMinViewBy_ident"+ (prop_updateMinViewBy_ident :: WTrie W -> Bool)+ , SC.testProperty+ "prop_updateMaxViewBy_ident"+ (prop_updateMaxViewBy_ident :: WTrie W -> Bool)+ -- HACK: must explicitly pass functions for these two, else they're too slow+ , SC.testProperty+ "prop_updateMinViewBy_gives_minAssoc"+ (prop_updateMinViewBy_gives_minAssoc undefined :: WTrie W -> Bool)+ , SC.testProperty+ "prop_updateMaxViewBy_gives_maxAssoc"+ (prop_updateMaxViewBy_gives_maxAssoc undefined :: WTrie W -> Bool)+ ]+ , Tasty.testGroup "toList (@()/@W)"+ -- These can handle depth 4, but it takes >1sec (but still <5sec)+ [ SC.testProperty+ "prop_toListBy_keysOrdered"+ (prop_toListBy_keysOrdered :: WTrie () -> Bool)+ , SC.testProperty+ "prop_keys"+ (prop_keys :: WTrie () -> Bool)+ , SC.testProperty+ "prop_elems"+ (prop_elems :: WTrie W -> Bool)+ -- TODO: move these into the section for 'Foldable', to agree with QC+ , SC.testProperty+ "prop_foldr_vs_foldrWithKey"+ (prop_foldr_vs_foldrWithKey :: WTrie W -> Bool)+#if MIN_VERSION_base(4,6,0)+ , SC.testProperty+ "prop_foldr_vs_foldr'"+ (prop_foldr_vs_foldr' :: WTrie W -> Bool)+ , SC.testProperty+ "prop_foldl_vs_foldl'"+ (prop_foldl_vs_foldl' :: WTrie W -> Bool)+#endif+ -- TODO: prop_foldMap_vs_foldMap' requires (Num W) if we want to use W.+ ]+ , Tasty.adjustOption (+ (1::SC.SmallCheckDepth))+ $ Tasty.testGroup "fromList (@()/@W)"+ [ SC.testProperty+ "prop_fromList_takes_first"+ (prop_fromList_takes_first :: [(WS, W)] -> Bool)+ , SC.testProperty+ "prop_fromListR_takes_first"+ (prop_fromListR_takes_first :: [(WS, W)] -> Bool)+ , SC.testProperty+ "prop_fromListL_takes_first"+ (prop_fromListL_takes_first :: [(WS, W)] -> Bool)+ , SC.testProperty+ "prop_fromListS_takes_first"+ (prop_fromListS_takes_first :: [(WS, W)] -> Bool)+ , SC.testProperty+ "prop_fromListWithConst_takes_first"+ (prop_fromListWithConst_takes_first :: [(WS, W)] -> Bool)+ , SC.testProperty+ "prop_fromListWithLConst_takes_first"+ (prop_fromListWithLConst_takes_first :: [(WS, W)] -> Bool)+ ]+ -- TODO: do we want to smallcheck any of the "Type classes" or "Other mapping/filtering" stuff we do in quickcheck?+ ]+++----------------------------------------------------------------+----------------------------------------------------------------++-- | 'TI.breakMaximalPrefix' satisfies the documented equalities.+prop_breakMaximalPrefix :: WS -> WS -> Bool+prop_breakMaximalPrefix (WS s) (WS z) =+ let (pre,s',z') = TI.breakMaximalPrefix s z+ in (pre <> s') == s+ && (pre <> z') == z++{-+-- FIXME: need to export the RLBS stuff if we are to test it...+prop_toStrict :: [WS] -> Bool+prop_toStrict =+ (S.concat .==. (TI.toStrict . foldl' (+>) Epsilon)) . map unWS+-}++----------------------------------------------------------------+-- | A singleton, is.+prop_singleton :: (Eq a) => WS -> a -> Bool+prop_singleton (WS k) v =+ T.singleton k v == T.insert k v T.empty++-- | If you insert a value, you can look it up.+prop_insert_lookup :: (Eq a) => WS -> a -> WTrie a -> Bool+prop_insert_lookup (WS k) v (WT t) =+ (T.lookup k . T.insert k v $ t) == Just v++-- | If you delete a value, you can't look it up.+prop_delete_lookup :: WS -> WTrie a -> Bool+prop_delete_lookup (WS k) =+ isNothing . T.lookup k . T.delete k . unWT+ where+ isNothing Nothing = True+ isNothing (Just _) = False++-- TODO: print/record diagnostics re what proportion of calls have+-- @n=0@ vs @n=1@, to ensure proper coverage.+prop_insert_size :: WS -> a -> WTrie a -> Bool+prop_insert_size (WS k) v (WT t) =+ ((T.size . T.insert k v) .==. ((n +) . T.size)) $ t+ where+ n = if T.member k t then 0 else 1++-- TODO: print/record diagnostics re what proportion of calls have+-- @n=0@ vs @n=1@, to ensure proper coverage.+prop_delete_size :: WS -> WTrie a -> Bool+prop_delete_size (WS k) (WT t) =+ ((T.size . T.delete k) .==. (subtract n . T.size)) $ t+ where+ n = if T.member k t then 1 else 0++prop_insert_delete :: (Eq a) => WS -> a -> WTrie a -> Bool+prop_insert_delete (WS k) v (WT t)+ | T.member k t = ((T.delete k . T.insert k v) .==. T.delete k) $ t+ | otherwise = ((T.delete k . T.insert k v) .==. id) $ t++-- | All keys in a submap are keys in the supermap+prop_submap_keysAreMembers :: WS -> WTrie a -> Bool+prop_submap_keysAreMembers (WS q) (WT t) =+ all (`T.member` t) . T.keys . T.submap q $ t+ -- TODO: should we use 'QC.conjoin' (assuming another class to overload it) in lieu of 'all'? What are the actual benefits of doing so? Ditto for all the uses below.++-- | All keys in a submap have the query as a prefix+prop_submap_keysHavePrefix :: WS -> WTrie a -> Bool+prop_submap_keysHavePrefix (WS q) =+ all (q `S.isPrefixOf`) . T.keys . T.submap q . unWT++-- | All values in a submap are the same in the supermap+prop_submap_valuesEq :: (Eq a) => WS -> WTrie a -> Bool+prop_submap_valuesEq (WS q) (WT t) =+ ((`T.lookup` t') .==. (`T.lookup` t)) `all` T.keys t'+ where t' = T.submap q t++-- | All keys in the result are keys in the supermap+prop_deleteSubmap_keysAreMembers :: WS -> WTrie a -> Bool+prop_deleteSubmap_keysAreMembers (WS q) (WT t) =+ all (`T.member` t) . T.keys . T.deleteSubmap q $ t++-- | All keys in a submap lack the query as a prefix+prop_deleteSubmap_keysLackPrefix :: WS -> WTrie a -> Bool+prop_deleteSubmap_keysLackPrefix (WS q) =+ all (not . S.isPrefixOf q) . T.keys . T.deleteSubmap q . unWT++-- | 'T.submap' and 'T.deleteSubmap' partition every trie for every key.+prop_deleteSubmap_disunion :: (Eq a) => WS -> WTrie a -> Bool+prop_deleteSubmap_disunion (WS q) (WT t) =+ t == (T.submap q t `TC.disunion` T.deleteSubmap q t)++prop_unionWith :: Eq a => (a -> a -> a) -> WTrie a -> WTrie a -> Bool+prop_unionWith f (WT x) (WT y) =+ TI.wip_unionWith f x y == TC.unionWith f x y++prop_unionL :: Eq a => WTrie a -> WTrie a -> Bool+prop_unionL = prop_unionWith (\x _ -> x)++prop_unionR :: Eq a => WTrie a -> WTrie a -> Bool+prop_unionR = prop_unionWith (\_ y -> y)++prop_unionPlus :: (Eq a, Num a) => WTrie a -> WTrie a -> Bool+prop_unionPlus = prop_unionWith (+)++-- TODO: other than as a helper like below, could we actually+-- generate interesting enough functions to make this worth testing+-- directly?+--+-- | Arbitrary @x ∩ y == (x ∪ y) ⋈ (x ⋈ y)@.+prop_intersectBy :: (Eq a) => (a -> a -> Maybe a) -> WTrie a -> WTrie a -> Bool+prop_intersectBy f (WT x) (WT y) =+ T.intersectBy f x y == (T.mergeBy f x y `TC.disunion` TC.disunion x y)++-- | Left-biased @x ∩ y == (x ∪ y) ⋈ (x ⋈ y)@.+prop_intersectL :: (Eq a) => WTrie a -> WTrie a -> Bool+prop_intersectL = prop_intersectBy (\x _ -> Just x)++-- | Right-biased @x ∩ y == (x ∪ y) ⋈ (x ⋈ y)@.+prop_intersectR :: (Eq a) => WTrie a -> WTrie a -> Bool+prop_intersectR = prop_intersectBy (\_ y -> Just y)++-- | Additive @x ∩ y == (x ∪ y) ⋈ (x ⋈ y)@.+prop_intersectPlus :: (Eq a, Num a) => WTrie a -> WTrie a -> Bool+prop_intersectPlus = prop_intersectBy (\x y -> Just (x + y))++isOrdered :: (Ord a) => [a] -> Bool+isOrdered xs = and (zipWith (<=) xs (drop 1 xs))++-- | 'T.toListBy', 'T.toList', and 'T.keys' are ordered by keys.+prop_toListBy_keysOrdered :: WTrie a -> Bool+prop_toListBy_keysOrdered = isOrdered . T.keys . unWT++fst3 :: (a,b,c) -> a+fst3 (a,_,_) = a++-- | 'T.matches' is ordered by keys.+prop_matches_keysOrdered :: WS -> WTrie a -> Bool+prop_matches_keysOrdered (WS q) (WT t) =+ isOrdered . map fst3 $ T.matches t q++-- | Matching keys are a prefix of the query.+prop_matches_keysArePrefix :: WS -> WTrie a -> Bool+prop_matches_keysArePrefix (WS q) (WT t) =+ all (`S.isPrefixOf` q) . map fst3 $ T.matches t q++_eqHead :: (Eq a) => Maybe a -> [a] -> Bool+_eqHead Nothing [] = True+_eqHead (Just x) (y:_) = x == y+_eqHead _ _ = False++_eqLast :: (Eq a) => Maybe a -> [a] -> Bool+_eqLast Nothing [] = True+_eqLast (Just x) ys@(_:_) = x == last ys+_eqLast _ _ = False++prop_minMatch_is_first_matches :: Eq a => WS -> WTrie a -> Bool+prop_minMatch_is_first_matches (WS q) (WT t) =+ _eqHead (T.minMatch t q) (T.matches t q)++prop_match_is_last_matches :: Eq a => WS -> WTrie a -> Bool+prop_match_is_last_matches (WS q) (WT t) =+ _eqLast (T.match t q) (T.matches t q)++prop_minAssoc_is_first_toList :: Eq a => WTrie a -> Bool+prop_minAssoc_is_first_toList (WT t) =+ _eqHead (TI.minAssoc t) (T.toList t)++prop_maxAssoc_is_last_toList :: Eq a => WTrie a -> Bool+prop_maxAssoc_is_last_toList (WT t) =+ _eqLast (TI.maxAssoc t) (T.toList t)++view2assoc :: Maybe (S.ByteString, a, T.Trie a) -> Maybe (S.ByteString, a)+view2assoc Nothing = Nothing+view2assoc (Just (k,v,_)) = Just (k,v)++-- TODO: again, can we actually generate any interesting functions here?+prop_updateMinViewBy_gives_minAssoc :: Eq a => (WS -> a -> Maybe a) -> WTrie a -> Bool+prop_updateMinViewBy_gives_minAssoc f =+ ((view2assoc . TI.updateMinViewBy (f . WS)) .==. TI.minAssoc) . unWT++prop_updateMaxViewBy_gives_maxAssoc :: Eq a => (WS -> a -> Maybe a) -> WTrie a -> Bool+prop_updateMaxViewBy_gives_maxAssoc f =+ ((view2assoc . TI.updateMaxViewBy (f . WS)) .==. TI.maxAssoc) . unWT++view2trie :: Maybe (S.ByteString, a, T.Trie a) -> T.Trie a+view2trie Nothing = T.empty+view2trie (Just (_,_,t)) = t++prop_updateMinViewBy_ident :: Eq a => WTrie a -> Bool+prop_updateMinViewBy_ident =+ ((view2trie . TI.updateMinViewBy (\_ v -> Just v)) .==. id) . unWT++prop_updateMaxViewBy_ident :: Eq a => WTrie a -> Bool+prop_updateMaxViewBy_ident =+ ((view2trie . TI.updateMaxViewBy (\_ v -> Just v)) .==. id) . unWT++prop_keys :: WTrie a -> Bool+prop_keys = (T.keys .==. (fmap fst . T.toList)) . unWT++prop_elems :: (Eq a) => WTrie a -> Bool+prop_elems = (T.elems .==. (fmap snd . T.toList)) . unWT++----------------------------------------------------------------+-- ~~~~~ 'Foldable' properties++-- * The laws as stated in the documentation.++prop_foldmapIdentity :: (Monoid m, Eq m) => WTrie m -> Bool+prop_foldmapIdentity = (F.fold .==. F.foldMap id) . unWT++prop_foldmapFusion :: (Monoid m, Eq m) => (a -> m) -> WTrie a -> Bool+prop_foldmapFusion f = (F.foldMap f .==. (F.fold . fmap f)) . unWT+ -- entails: foldMap f . fmap g ≡ foldMap (f . g)++{- -- TODO:+foldr f z t ≡ appEndo (F.foldMap (Endo . f) t ) z+foldl f z t ≡ appEndo (getDual (F.foldMap (Dual . Endo . flip f) t)) z+-}++-- By definition we already have @T.size ≡ F.length@.+prop_length :: WTrie a -> Bool+prop_length = (F.length .==. getSum . F.foldMap (Sum . const 1)) . unWT++{- -- TODO: handle the cases of old @base@+prop_sum :: (Num a, Eq a) => WTrie a -> Bool+prop_sum = (F.sum .==. (getSum . F.foldMap' Sum)) . unWT++prop_product :: (Num a, Eq a) => WTrie a -> Bool+prop_product = (F.product .==. (getProduct . F.foldMap' Product)) . unWT++-- These two laws aren't stated in the documentation, but obviously should be.+prop_minimum :: (Ord a, Eq a) => NonEmpty (WTrie a) -> Bool+prop_minimum = (F.minimum .==. (getMin . F.foldMap' Min)) . unWT++prop_maximum :: (Ord a, Eq a) => NonEmpty (WTrie a) -> Bool+prop_maximum = (F.maximum .==. (getMax . F.foldMap' Max)) . unWT+-}+++-- * At least make sure these at least have the same order.+-- TODO: better equivalence tests++prop_foldr_vs_foldrWithKey :: Eq a => WTrie a -> Bool+prop_foldr_vs_foldrWithKey =+ (F.foldr (:) [] .==. TI.foldrWithKey (const (:)) []) . unWT++prop_foldr'_vs_foldrWithKey' :: Eq a => WTrie a -> Bool+prop_foldr'_vs_foldrWithKey' =+ (F.foldr' (:) [] .==. TI.foldrWithKey' (const (:)) []) . unWT++prop_foldl_vs_foldlWithKey :: Eq a => WTrie a -> Bool+prop_foldl_vs_foldlWithKey =+ (F.foldl snoc [] .==. TI.foldlWithKey (const . snoc) []) . unWT+ where+ snoc = flip (:)++prop_foldl'_vs_foldlWithKey' :: Eq a => WTrie a -> Bool+prop_foldl'_vs_foldlWithKey' =+ (F.foldl' snoc [] .==. TI.foldlWithKey' (const . snoc) []) . unWT+ where+ snoc = flip (:)++#if MIN_VERSION_base(4,6,0)+prop_foldr_vs_foldr' :: (Eq a) => WTrie a -> Bool+prop_foldr_vs_foldr' = (F.foldr (:) [] .==. F.foldr' (:) []) . unWT++prop_foldl_vs_foldl' :: (Eq a) => WTrie a -> Bool+prop_foldl_vs_foldl' = (F.foldl snoc [] .==. F.foldl' snoc []) . unWT+ where+ snoc = flip (:)+#endif++#if MIN_VERSION_base(4,13,0)+-- TODO: use a non-commutative Monoid, to ensure the order is the same.+prop_foldMap_vs_foldMap' :: (Num a, Eq a) => WTrie a -> Bool+prop_foldMap_vs_foldMap' = (F.foldMap Sum .==. F.foldMap' Sum) . unWT+#endif++-- TODO: how can we best test that fold{l,r,Map}{,'} are sufficiently lazy\/strict?+-- See: <https://github.com/haskell/containers/blob/master/containers-tests/tests/intmap-strictness.hs>++-- TODO: #if MIN_VERSION_base(4,8,0), check that 'F.null' isn't cyclic definition++----------------------------------------------------------------+-- ~~~~~ 'Traversable' laws++#ifndef HideUnusedTopBonds+-- | Horizontal composition of Kleisli arrows.+under :: Functor f => (b -> g c) -> (a -> f b) -> a -> Compose f g c+under g f = Compose . fmap g . f+#endif++{-+-- TODO: We can't really autogenerate these, but maybe we could+-- have a select few interesting ones?+--+-- | An \"applicative transformation\".+data AT f g where+ AT :: (Applicative f, Applicative g) => forall a. f a -> g a+ -- Such that:+ -- @t (pure x) ≡ pure x@+ -- @t (f <*> x) ≡ t f <*> t x@++prop_TraverseNaturality+ :: (Applicative f, Applicative g, Eq1 g, Eq b)+ => AT f g -> (a -> f b) -> WTrie a -> Bool+prop_TraverseNaturality (AT t) f =+ ((t . traverse f) .==. traverse (t . f)) . unWT+-}++prop_TraverseIdentity :: Eq a => WTrie a -> Bool+prop_TraverseIdentity = (traverse Identity .==. Identity) . unWT++-- TODO: how could we actually test this? Any good @f,g@ to try in particular?+#ifndef HideUnusedTopBonds+prop_TraverseComposition+ :: (Applicative f, Applicative g, Eq1 g, Eq1 f, Eq c)+ => (b -> g c) -> (a -> f b) -> WTrie a -> Bool+prop_TraverseComposition g f =+ (traverse (g `under` f) .==. (traverse g `under` traverse f)) . unWT+#endif++-- NOTE: The next two are redundant, since we don't provide a bespoke+-- implementation of 'sequenceA'.++{-+prop_SequenceaNaturality+ :: (Applicative f, Applicative g, Eq1 g, Eq a)+ => AT f g -> WTrie (f a) -> Bool+prop_SequenceaNaturality (AT t) =+ ((t . sequenceA) .==. (sequenceA . fmap t)) . unWT+-}++prop_SequenceaIdentity :: Eq a => WTrie a -> Bool+prop_SequenceaIdentity =+ ((sequenceA . fmap Identity) .==. Identity) . unWT++-- TODO: how could we actually test this? Any good @f,g@ to try in particular?+#ifndef HideUnusedTopBonds+prop_SequenceaComposition+ :: (Applicative f, Applicative g, Eq1 g, Eq1 f, Eq a)+ => WTrie (g (f a)) -> Bool+prop_SequenceaComposition =+ ((sequenceA . fmap Compose) .==. (sequenceA `under` sequenceA)) . unWT+#endif++-- TODO: reiterate the laws for 'traverseWithKey'...++----------------------------------------------------------------+-- ~~~~~ 'Filterable' laws+-- TODO: how could we actually test these effectively? Instead of+-- just with particular example functions?++-- | Shorthand alias for a filtering function.+type F a b = a -> Maybe b++-- | Shorthand alias for a predicate function.+type P a = a -> Bool++prop_FiltermapFission :: Eq b => F a b -> WTrie a -> Bool+prop_FiltermapFission f =+ (T.filterMap f .==. (fmap (fromJust . f) . TI.filter (isJust . f))) . unWT++prop_FiltermapFusion :: Eq b => (a -> b) -> P a -> WTrie a -> Bool+prop_FiltermapFusion f g =+ ((fmap f . TI.filter g) .==. T.filterMap (\v -> f v <$ guard (g v))) . unWT++prop_FiltermapConservation :: Eq b => (a -> b) -> WTrie a -> Bool+prop_FiltermapConservation f =+ (T.filterMap (Just . f) .==. fmap f) . unWT++#ifndef HideUnusedTopBonds+prop_FiltermapVertComposition :: Eq c => F b c -> F a b -> WTrie a -> Bool+prop_FiltermapVertComposition f g =+ ((T.filterMap f . T.filterMap g) .==. T.filterMap (f <=< g)) . unWT+#endif++prop_FilterDefinition :: Eq a => P a -> WTrie a -> Bool+prop_FilterDefinition f =+ (TI.filter f .==. T.filterMap (\v -> v <$ guard (f v))) . unWT++prop_FilterVertComposition :: Eq a => P a -> P a -> WTrie a -> Bool+prop_FilterVertComposition f g =+ ((TI.filter f . TI.filter g) .==. TI.filter (liftA2 (&&) f g)) . unWT++-- A handful of interesting predicates...+isTriple :: Integral a => P a+isTriple x = rem x 3 == 0++remTriple :: Integral a => F a a+remTriple x+ | r == 0 = Nothing+ | otherwise = Just r+ where r = rem x 3++#ifndef HideUnusedTopBonds+quotTriple :: Integral a => F a a+quotTriple x+ | r == 0 = Just q+ | otherwise = Nothing+ where (q,r) = quotRem x 3+#endif++----------------------------------------------------------------+-- ~~~~~ 'Witherable' laws+-- TODO: how could we actually test these effectively?++#ifndef HideUnusedTopBonds+-- | Shorthand alias for a withering function.+type FA a f b = a -> f (Maybe b)+#endif++{-+prop_WitherNaturality+ :: (Applicative f, Applicative g, Eq1 g, Eq b)+ => AT f g -> FA a f b -> WTrie a -> Bool+prop_WitherNaturality (AT t) f =+ (TI.wither (t . f) .==. (t . TI.wither f)) . unWT+-}++{-+-- TODO: how can we nail down the functor @f@ but in a way that's portable beyond GHC?+prop_WitherPurity :: (Applicative f, Eq1 f, Eq b) => F a b -> WTrie a -> Bool+prop_WitherPurity f =+ (TI.wither (pure . f) .==. (pure . T.filterMap f)) . unWT+-}++-- HACK: adding the 'Compose' wrapper to force GHC to deduce @Eq+-- (f (T.Trie b))@ from the provided @(Eq1 f, Eq b)@.+prop_WitherConservation+ :: (Applicative f, Eq1 f, Eq b) => (a -> f b) -> WTrie a -> Bool+prop_WitherConservation f =+ ((Compose . TI.wither (fmap Just . f)) .==. (Compose . traverse f)) . unWT++#ifndef HideUnusedTopBonds+prop_WitherHorizComposition+ :: (Applicative f, Applicative g, Eq1 g, Eq1 f, Eq c)+ => FA b g c -> FA a f b -> WTrie a -> Bool+prop_WitherHorizComposition f g =+ ((TI.wither f `under` TI.wither g) .==. TI.wither (wither_Maybe f `under` g)) . unWT++-- | Variant of wither for Maybe instead of Trie.+wither_Maybe :: Applicative f => FA a f b -> Maybe a -> f (Maybe b)+wither_Maybe f = fmap join . traverse f+#endif++-- | Shorthand alias for an effectful predicate function.+type PA a f = a -> f Bool++-- HACK: adding the 'Compose' wrapper to force GHC to deduce @Eq+-- (f (T.Trie a))@ from the provided @(Eq1 f, Eq a)@.+prop_FilteraDefinition :: (Applicative f, Eq1 f, Eq a) => PA a f -> WTrie a -> Bool+prop_FilteraDefinition f =+ ((Compose . TI.filterA f) .==. (Compose . TI.wither (\v -> (v <$) . guard <$> f v))) . unWT++{-+prop_FilteraNaturality+ :: (Applicative f, Applicative g, Eq1 g, Eq a)+ => AT f g -> PA a f -> WTrie a -> Bool+prop_FilteraNaturality (AT t) f =+ (TI.filterA (t . f) .==. (t . TI.filterA f)) . unWT+-}++{-+-- TODO: how can we nail down the @f@ but in a way that's portable beyond GHC?+prop_FilteraPurity :: (Functor f, Eq1 f, Eq a) => P a -> WTrie a -> Bool+prop_FilteraPurity f =+ (TI.filterA (pure . f) .==. (pure . TI.filter f)) . unWT+-}++#ifndef HideUnusedTopBonds+prop_FilteraHorizComposition+ :: (Applicative f, Applicative g, Eq1 g, Eq1 f, Eq a)+ => PA a f -> PA a g -> WTrie a -> Bool+prop_FilteraHorizComposition f g =+ ((TI.filterA f `under` TI.filterA g) .==. TI.filterA (underA2 (&&) f g)) . unWT++underA2 :: (Applicative f, Applicative g)+ => (b -> c -> d)+ -> (a -> g b)+ -> (a -> f c)+ -> a -> Compose f g d+underA2 h g f = liftA2 (liftA2 h) (g `under` pure) (pure `under` f)++prop_FilteraHorizComposition_alt+ :: (Applicative f, Applicative g, Eq1 g, Eq1 f, Eq a)+ => PA a f -> PA a g -> WTrie a -> Bool+prop_FilteraHorizComposition_alt f g =+ ((TI.filterA f `under` TI.filterA g) .==. TI.filterA (underF2 (&&) g f)) . unWT++underF2 :: (Functor f, Functor g)+ => (b -> c -> d)+ -> (a -> f b)+ -> (a -> g c)+ -> a -> Compose f g d+underF2 h f g a = Compose (f a <&> ((g a <&>) . h))+#endif+++----------------------------------------------------------------+-- | If there are duplicate keys in the @assocs@, then @f@ will+-- take the first value.+_takes_first :: (Eq c) => ([(S.ByteString, c)] -> T.Trie c) -> [(WS, c)] -> Bool+_takes_first f assocs =+ (T.toList . f) .==. (nubBy (apFst (==)) . sortBy (comparing fst))+ $ map (first unWS) assocs++-- | Lift a function to apply to the 'fst' of pairs, retaining the 'snd'.+first :: (a -> b) -> (a,c) -> (b,c)+first f (x,y) = (f x, y)++-- | Lift a function to apply to the 'snd' of pairs, retaining the 'fst'.+second :: (b -> c) -> (a,b) -> (a,c)+second f (x,y) = (x, f y)++-- | Lift a binary function to apply to the first of pairs, discarding seconds.+apFst :: (a -> b -> c) -> ((a,d) -> (b,e) -> c)+apFst f (x,_) (y,_) = f x y++-- | 'T.fromList' takes the first value for a given key.+prop_fromList_takes_first :: (Eq a) => [(WS, a)] -> Bool+prop_fromList_takes_first = _takes_first T.fromList++-- | 'T.fromListR' takes the first value for a given key.+prop_fromListR_takes_first :: (Eq a) => [(WS, a)] -> Bool+prop_fromListR_takes_first = _takes_first TC.fromListR++-- | 'T.fromListL' takes the first value for a given key.+prop_fromListL_takes_first :: (Eq a) => [(WS, a)] -> Bool+prop_fromListL_takes_first = _takes_first TC.fromListL++-- | 'T.fromListS' takes the first value for a given key.+prop_fromListS_takes_first :: (Eq a) => [(WS, a)] -> Bool+prop_fromListS_takes_first = _takes_first TC.fromListS++-- | @('TC.fromListWith' const)@ takes the first value for a given key.+prop_fromListWithConst_takes_first :: (Eq a) => [(WS, a)] -> Bool+prop_fromListWithConst_takes_first = _takes_first (TC.fromListWith const)++-- | @('TC.fromListWithL' const)@ takes the first value for a given key.+prop_fromListWithLConst_takes_first :: (Eq a) => [(WS, a)] -> Bool+prop_fromListWithLConst_takes_first = _takes_first (TC.fromListWithL const)++----------------------------------------------------------------+-- ~~~~~ 'Functor' properties++prop_FunctorIdentity :: Eq a => WTrie a -> Bool+prop_FunctorIdentity = (fmap id .==. id) . unWT++{- -- TODO: is there any way to make this remotely testable?+prop_FunctorCompose :: Eq c => (b -> c) -> (a -> b) -> WTrie a -> Bool+prop_FunctorCompose f g = (fmap (f . g) .==. (fmap f . fmap g)) . unWT+-}++#if __GLASGOW_HASKELL__+-- | @(<$)@ agrees with its default definition.+prop_fmapConst :: Eq a => a -> WTrie b -> Bool+prop_fmapConst v = ((v <$) .==. fmap (const v)) . unWT+#endif++{-+-- Both of these test only a subset of what 'prop_fmap_toList' tests. I was hoping they'd help simplify the function-generation problem, but if we're testing 'prop_fmap_toList' anyways then there's no point in testing these too.++-- | 'fmap' doesn't affect the keys. This is safe to call with an+-- undefined function, thereby proving that the function cannot+-- affect things.+prop_fmap_keys :: Eq b => (a -> b) -> WTrie a -> Bool+prop_fmap_keys f = ((T.keys . fmap f) .==. T.keys) . unWT++prop_fmap_elems :: Eq b => (a -> b) -> WTrie a -> Bool+prop_fmap_elems f = ((T.elems . fmap f) .==. (map f . T.elems)) . unWT+-}++-- TODO: is there any way to generate halfway useful functions for testing here?+prop_fmap_toList :: Eq b => (a -> b) -> WTrie a -> Bool+prop_fmap_toList f =+ ((T.toList . fmap f) .==. (map (second f) . T.toList)) . unWT++----------------------------------------------------------------+prop_filterMap_ident :: Eq a => WTrie a -> Bool+prop_filterMap_ident = (T.filterMap Just .==. id) . unWT++prop_filterMap_empty :: Eq a => WTrie a -> Bool+prop_filterMap_empty = (T.filterMap const_Nothing .==. const T.empty) . unWT+ where+ -- Have to fix the result type here.+ const_Nothing :: a -> Maybe a+ const_Nothing = const Nothing++justConst :: a -> b -> Maybe a+justConst x _ = Just x++prop_mapBy_keys :: WTrie a -> Bool+prop_mapBy_keys = all (uncurry (==)) . T.toList . T.mapBy justConst . unWT++prop_contextualMap_ident :: Eq a => WTrie a -> Bool+prop_contextualMap_ident = (TI.contextualMap const .==. id) . unWT++prop_contextualMap'_ident :: Eq a => WTrie a -> Bool+prop_contextualMap'_ident = (TI.contextualMap' const .==. id) . unWT++prop_contextualFilterMap_ident :: Eq a => WTrie a -> Bool+prop_contextualFilterMap_ident =+ (TI.contextualFilterMap justConst .==. id) . unWT++prop_contextualMapBy_keys :: WTrie a -> Bool+prop_contextualMapBy_keys =+ all (uncurry (==)) . T.toList . TI.contextualMapBy f . unWT+ where+ f k _ _ = Just k++prop_contextualMapBy_ident :: Eq a => WTrie a -> Bool+prop_contextualMapBy_ident = (TI.contextualMapBy f .==. id) . unWT+ where+ f _ v _ = Just v++prop_contextualMapBy_empty :: Eq a => WTrie a -> Bool+prop_contextualMapBy_empty = (TI.contextualMapBy f .==. const T.empty) . unWT+ where+ -- Have to fix the result type here.+ f :: S.ByteString -> a -> T.Trie a -> Maybe a+ f _ _ _ = Nothing++----------------------------------------------------------------+-- ~~~~~ 'Applicative' laws++prop_ApplicativeIdentity :: Eq a => WTrie a -> Bool+prop_ApplicativeIdentity = ((pure id <*>) .==. id) . unWT++{- -- (remaining, untestable) Applicative laws+prop_ApplicativeCompose = pure (.) <*> u <*> v <*> w == u <*> (v <*> w)+prop_ApplicativeHom = pure f <*> pure x == pure (f x)+prop_ApplicativeInterchange = u <*> pure y == pure ($ y) <*> u+-}++----------------------------------------------------------------+-- ~~~~~ 'Monad' laws++prop_MonadIdentityR :: Eq a => WTrie a -> Bool+prop_MonadIdentityR = ((>>= return) .==. id) . unWT++{- -- (remaining, untestable) Monad laws+prop_MonadIdentityL = (return a >>= k) == k a+prop_MonadAssoc = m >>= (\x -> k x >>= h) == (m >>= k) >>= h+-}++----------------------------------------------------------------+-- ~~~~~ 'Semigroup' laws++#if MIN_VERSION_base(4,9,0)+prop_Semigroup :: (Semigroup a, Eq a) => WTrie a -> WTrie a -> WTrie a -> Bool+prop_Semigroup (WT a) (WT b) (WT c) = a <> (b <> c) == (a <> b) <> c++-- TODO: laws for 'stimes'+#endif++----------------------------------------------------------------+-- ~~~~~ 'Monoid' laws++-- N.B., base-4.11.0.0 is when Semigroup became superclass of Monoid+prop_MonoidIdentityL :: (Monoid a, Eq a) => WTrie a -> Bool+prop_MonoidIdentityL = ((mempty `mappend`) .==. id) . unWT++prop_MonoidIdentityR :: (Monoid a, Eq a) => WTrie a -> Bool+prop_MonoidIdentityR = ((`mappend` mempty) .==. id) . unWT++----------------------------------------------------------------+-- ~~~~~ 'Binary' laws++prop_Binary :: (B.Binary a, Eq a) => WTrie a -> Bool+prop_Binary = ((B.decode . B.encode) .==. id) . unWT++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ dev/Test/Utils.hs view
@@ -0,0 +1,254 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE CPP+ , MultiParamTypeClasses+ , FlexibleInstances+ , FlexibleContexts+ #-}++----------------------------------------------------------------+-- ~ 2021.12.14+-- |+-- Module : Test.Utils+-- Copyright : 2008--2023 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : provisional+-- Portability : semi-portable (MPTC,...)+--+-- Utilities for testing 'Trie's.+----------------------------------------------------------------+module Test.Utils+ ( packC2W, vocab2trie+ , testEqual+ , W(..), everyW+ , WS(..), packWS, unpackWS+ , WTrie(..)+ , CheckGuard(..), (.==>.), (.==.)+ ) where++import qualified Data.Trie as T+import Data.Word (Word8)+import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S (c2w, w2c)+import Data.ByteString.Internal (ByteString(PS))+import Control.Monad ((<=<))++-- N.B., "Test.Tasty.HUnit" does not in fact depend on "Test.HUnit";+-- hence using the longer alias.+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.HUnit as TastyHU+import qualified Test.QuickCheck as QC+import qualified Test.SmallCheck as SC+import qualified Test.SmallCheck.Series as SC+-- import qualified Test.LazySmallCheck as LSC+-- import qualified Test.SparseCheck as PC++----------------------------------------------------------------+----------------------------------------------------------------++-- TODO: apparently this is exported as 'Data.ByteString.Internal.packChars'+-- | Construct a bytestring from the first byte of each 'Char'.+packC2W :: String -> S.ByteString+packC2W = S.pack . map S.c2w++-- | Construct a trie via 'packC2W' giving each key a unique value+-- (namely its position in the list).+vocab2trie :: [String] -> T.Trie Int+vocab2trie = T.fromList . flip zip [0..] . map packC2W++----------------------------------------------------------------+-- TODO: come up with a thing that pretty-prints the diff, instead+-- of just showing the expected\/actual.+testEqual :: (Show a, Eq a) => String -> a -> a -> Tasty.TestTree+testEqual name expected actual =+ TastyHU.testCase name (TastyHU.assertEqual "" expected actual)++----------------------------------------------------------------+-- | A small subset of 'Word8', so that 'WS' is more likely to have+-- shared prefixes. The 'Show' instance shows it as a 'Char', for+-- better legibility and for consistency with the 'Show' instance+-- of 'WS'.+newtype W = W { unW :: Word8 }+ deriving (Eq, Ord)++instance Show W where+ showsPrec p = showsPrec p . S.w2c . unW++-- TODO: ensure that these have good bit-patterns for covering corner cases.+-- | All the possible 'W' values; or rather, all the ones generated+-- by the 'QC.Arbitrary' and 'SC.Serial' instances.+everyW :: [W]+everyW = (W . S.c2w) <$> ['a'..'m']++-- TODO: if we define (Enum W) then we could use 'QC.chooseEnum'+-- which is much faster than 'QC.elements'. Alternatively we might+-- consider using 'QC.growingElements' if we want something more+-- like what the SC.Serial case does.+instance QC.Arbitrary W where+ arbitrary = QC.elements everyW+ shrink w = takeWhile (w /=) everyW++instance QC.CoArbitrary W where+ coarbitrary = QC.coarbitrary . unW++-- We take @(d+1)@ to match the instances for 'Char', (SC.N a), etc+instance Monad m => SC.Serial m W where+ series = SC.generate (\d -> take (d+1) everyW)++instance Monad m => SC.CoSerial m W where+ coseries = fmap (. unW) . SC.coseries++----------------------------------------------------------------+-- TODO: we need a better instance of Arbitrary for lists to make+-- them longer than our smallcheck depth.+--+-- | A subset of 'S.ByteString' produced by 'packWS'.+-- This newtype is to ensure that generated bytestrings are more+-- likely to have shared prefixes (and thus non-trivial tries).+newtype WS = WS { unWS :: S.ByteString }+ deriving (Eq, Ord)++instance Show WS where+ showsPrec p = showsPrec p . unWS++packWS :: [W] -> WS+packWS = WS . S.pack . map unW++unpackWS :: WS -> [W]+unpackWS = map W . S.unpack . unWS++-- | Like 'S.inits' but each step keeps half more, rather than just one more.+prefixes :: WS -> [WS]+prefixes (WS (PS x s l)) =+ [WS (PS x s (l - k)) | k <- takeWhile (> 0) (iterate (`div` 2) l)]++instance QC.Arbitrary WS where+ arbitrary = QC.sized $ \n -> do+ k <- QC.chooseInt (0,n)+ xs <- QC.vector k+ return $ packWS xs+ shrink = QC.shrinkMap packWS unpackWS <=< prefixes++instance QC.CoArbitrary WS where+ coarbitrary = QC.coarbitrary . unpackWS++instance Monad m => SC.Serial m WS where+ series = packWS <$> SC.series++-- TODO: While this is a perfectly valid instance, is it really the+-- most efficient one for our needs?+instance Monad m => SC.CoSerial m WS where+ coseries rs =+ SC.alts0 rs SC.>>- \z ->+ SC.alts2 rs SC.>>- \f ->+ return $ \(WS xs) ->+ if S.null xs+ then z+ else f (W $ S.head xs) (WS $ S.tail xs)++----------------------------------------------------------------+-- | A subset of 'T.Trie' where all the keys are 'WS'. This newtype+-- is mainly just to avoid orphan instances.+newtype WTrie a = WT { unWT :: T.Trie a }+ deriving (Eq)++instance Show a => Show (WTrie a) where+ showsPrec p = showsPrec p . unWT++first :: (b -> c) -> (b,d) -> (c,d)+first f (x,y) = (f x, y)++-- TODO: maybe we ought to define @T.fromListBy@ for better fusion?+fromListWT :: [(WS,a)] -> WTrie a+fromListWT = WT . T.fromList . map (first unWS)++-- We can use 'T.toListBy' to manually fuse with the map+toListWT :: WTrie a -> [(WS,a)]+toListWT = map (first WS) . T.toList . unWT++instance (QC.Arbitrary a) => QC.Arbitrary (WTrie a) where+ arbitrary = QC.sized $ \n -> do+ k <- QC.chooseInt (0,n)+ labels <- QC.vector k+ elems <- QC.vector k+ return . fromListWT $ zip labels elems+ -- Extremely inefficient, but should be effective at least.+ shrink = QC.shrinkMap fromListWT toListWT++-- TODO: instance QC.CoArbitrary (WTrie a)++-- TODO: This instance really needs some work. The smart constructures+-- ensure only valid values are generated, but there are redundancies+-- and inefficiencies.+instance (Monad m, SC.Serial m a) => SC.Serial m (WTrie a) where+ series = SC.cons0 (WT T.empty)+ SC.\/ SC.cons3 arcHACK+ SC.\/ SC.cons2 branch+ where+ arcHACK (WS k) mv (WT t) =+ case mv of+ Nothing -> WT (T.singleton k () >> t)+ Just v -> WT (T.singleton k v >>= T.unionR t . T.singleton S.empty)++ branch (WT t0) (WT t1) = WT (t0 `T.unionR` t1)++-- TODO: instance Monad m => SC.CoSerial m (WTrie a)++----------------------------------------------------------------+----------------------------------------------------------------++infixr 0 ==>, .==>.+infix 4 .==.++{-+-- TODO: clean up something like this:+class ForAll src p q where+ forAll :: forall a. (Show a) => src a -> (a -> p) -> q+instance (QC.Testable p) => ForAll QC.Gen p QC.Property where+ forAll gen pf = QC.forAllShrink gen QC.shrink pf+instance (SC.Testable m p) => ForAll (SC.Series m) p (SC.Property m) where+ forAll srs pf = SC.forAll (SC.over srs pf)++class SuchThat src where+ suchThat :: forall a. src a -> (a -> Bool) -> src a+instance SuchThat QC.Gen where+ suchThat = QC.suchThat+instance SuchThat (SC.Series m) where+ suchThat = flip Control.Monad.mfilter++class Generable src a where+ generate :: src a+instance (QC.Arbitrary a) => Generable QC.Gen a where+ generate = QC.arbitrary+instance (SC.Serial m a) => Generable (SC.Series m) a where+ generate = SC.series++forEach :: (Forall src p q, SuchThat src, Generable src a, Show a) => (a -> Bool) -> (a -> p) -> q+forEach = forAll . suchThat generate+-}+++-- | Deal with QC\/SC polymorphism issues because of @(==>)@.+-- Fundeps would be nice here, but @|b->a@ is undecidable, and @|a->b@ is wrong.+class CheckGuard p q where+ (==>) :: Bool -> p -> q++instance (QC.Testable p) => CheckGuard p QC.Property where+ (==>) = (QC.==>)+ -- TODO: might should also use 'QC.cover' with this.+ -- TODO: or we may prefer to rephrase things to use 'QC.suchThat' instead (should be sufficient for our particular use case, if we can find a smallcheck analogue (probably 'SC.over'))++instance (Monad m, SC.Testable m p) => CheckGuard p (SC.Property m) where+ (==>) = (SC.==>)++-- | Lifted implication.+(.==>.) :: CheckGuard testable prop => (a -> Bool) -> (a -> testable) -> (a -> prop)+(.==>.) p q x = p x ==> q x++-- | Function equality / lifted equality.+(.==.) :: (Eq b) => (a -> b) -> (a -> b) -> (a -> Bool)+(.==.) f g x = f x == g x+ -- TODO: should use (QC.===) or diy with QC.counterexample; assuming we can overload that for smallcheck equivalent (or for smallcheck to ignore and fall back to (==))++----------------------------------------------------------------+----------------------------------------------------------- fin.
src/Data/Trie.hs view
@@ -1,14 +1,16 @@--- To make GHC stop warning about the Prelude-{-# OPTIONS_GHC -Wall -fwarn-tabs -fno-warn-unused-imports #-}-{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# LANGUAGE NoImplicitPrelude, CPP #-}+#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE Safe #-}+#endif ------------------------------------------------------------------- ~ 2021.10.17+-- ~ 2021.12.14 -- | -- Module : Data.Trie--- Copyright : Copyright (c) 2008--2021 wren gayle romano--- License : BSD3+-- Copyright : 2008--2025 wren romano+-- License : BSD-3-Clause -- Maintainer : wren@cpan.org--- Stability : experimental+-- Stability : provisional -- Portability : portable -- -- An efficient implementation of finite maps from strings to values.@@ -29,7 +31,7 @@ -- detailed enough for most users. For an extended interface with -- many additional functions, see "Data.Trie.Convenience". For -- functions that give more detailed (potentially abstraction-breaking)--- access to the data strucuture, or for experimental functions+-- access to the data structure, or for experimental functions -- which aren't quite ready for the public API, see "Data.Trie.Internal". ---------------------------------------------------------------- @@ -45,27 +47,26 @@ , fromList, toListBy, toList, keys, elems -- * Query functions- , lookupBy, lookup, member, submap, match, matches+ , lookupBy, lookup, member, submap, match, minMatch, matches - -- * Single-value modification- , alterBy, insert, adjust, delete+ -- * Simple modification+ , insert, adjust, adjustBy, alterBy, delete, deleteSubmap -- * Combining tries , mergeBy, unionL, unionR+ , intersectBy, intersectL, intersectR -- * Mapping functions , mapBy, filterMap+ -- TODO: re-export 'filter', 'filterA', 'wither', etc? ) where import Prelude hiding (null, lookup)-import qualified Prelude (null, lookup) import Data.Trie.Internal-import Data.Trie.Errors (impossible) import Data.ByteString (ByteString) import qualified Data.ByteString as S import Data.Maybe (isJust)-import Control.Monad (liftM) ---------------------------------------------------------------- ---------------------------------------------------------------- @@ -74,28 +75,13 @@ -- Conversion functions ---------------------------------------------------------------} --- | Convert association list into a trie. On key conflict, values--- earlier in the list shadow later ones.-fromList :: [(ByteString,a)] -> Trie a-{-# INLINE fromList #-}-fromList = foldr (uncurry insert) empty---- | Convert trie into association list. Keys will be in sorted order.-toList :: Trie a -> [(ByteString,a)]-{-# INLINE toList #-}-toList = toListBy (,)---- FIX? should 'keys' and 'elems' move to Data.Trie.Convenience instead?- -- | Return all keys in the trie, in sorted order.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>. keys :: Trie a -> [ByteString] {-# INLINE keys #-} keys = toListBy const --- | Return all values in the trie, in sorted order according to the keys.-elems :: Trie a -> [a]-{-# INLINE elems #-}-elems = toListBy (flip const) {---------------------------------------------------------------@@ -106,46 +92,58 @@ -- rooted at the prefix. lookupBy :: (Maybe a -> Trie a -> b) -> ByteString -> Trie a -> b {-# INLINE lookupBy #-}-lookupBy f = lookupBy_ f (f Nothing empty) (f Nothing)+lookupBy f = lookupBy_ (f . Just) (f Nothing) (f Nothing empty) -- | Return the value associated with a query string if it exists. lookup :: ByteString -> Trie a -> Maybe a {-# INLINE lookup #-}-lookup = lookupBy_ const Nothing (const Nothing)+lookup = lookupBy const --- TODO? move to "Data.Trie.Convenience"? -- | Does a string have a value in the trie? member :: ByteString -> Trie a -> Bool {-# INLINE member #-} member q = isJust . lookup q +getMatch :: ByteString -> (Int, a) -> (ByteString, a, ByteString)+{-# INLINE getMatch #-}+getMatch q (n,x) =+ case S.splitAt n q of+ (p,q') -> (p, x, q') -- | Given a query, find the longest prefix with an associated value--- in the trie, returning that prefix, it's value, and the remaining--- string.+-- in the trie, and return that prefix, its value, and the remainder+-- of the query.+--+-- @since 0.2.4 match :: Trie a -> ByteString -> Maybe (ByteString, a, ByteString)-match t q =- case match_ t q of- Nothing -> Nothing- Just (n,x) ->- case S.splitAt n q of- (p,q') -> Just (p, x, q')+{-# INLINE match #-}+match t q = getMatch q <$> match_ t q +-- | Given a query, find the shortest prefix with an associated value+-- in the trie, and return that prefix, its value, and the remainder+-- of the query.+--+-- @since 0.2.6+minMatch :: Trie a -> ByteString -> Maybe (ByteString, a, ByteString)+{-# INLINE minMatch #-}+minMatch t q =+ case matches t q of+ [] -> Nothing+ x:_ -> Just x -- | Given a query, find all prefixes with associated values in the--- trie, returning the prefixes, their values, and their remaining--- strings. This function is a good producer for list fusion.+-- trie, and return their (prefix, value, remainder) triples in+-- order from shortest prefix to longest. This function is a good+-- producer for list fusion.+--+-- @since 0.2.4 matches :: Trie a -> ByteString -> [(ByteString, a, ByteString)] {-# INLINE matches #-}-matches t q = map f (matches_ t q)- where- f (n,x) =- case S.splitAt n q of- (p,q') -> (p, x, q')+matches t q = getMatch q <$> matches_ t q {------------------------------------------------------------------ Single-value modification functions (recurse and clone spine)+-- Simple modification functions (recurse and clone spine) ---------------------------------------------------------------} -- | Insert a new key. If the key is already present, overrides the@@ -154,33 +152,62 @@ {-# INLINE insert #-} insert = alterBy (\_ x _ -> Just x) --- | Apply a function to the value at a key.-adjust :: (a -> a) -> ByteString -> Trie a -> Trie a-{-# INLINE adjust #-}-adjust f q = adjustBy (\_ _ -> f) q (impossible "adjust")--- TODO: benchmark vs the definition with alterBy/liftM+-- | Alter the value associated with a given key. If the key is not+-- present, then the trie is returned unaltered. See 'alterBy' if+-- you are interested in inserting new keys or deleting old keys.+-- Because this function does not need to worry about changing the+-- trie structure, it is somewhat faster than 'alterBy'.+--+-- @since 0.2.6+-- __Note__: Prior to version 0.2.6 this function was exported from+-- "Data.Trie.Internal" instead.+adjustBy :: (ByteString -> a -> a -> a)+ -> ByteString -> a -> Trie a -> Trie a+{-# INLINE adjustBy #-}+adjustBy f q x = adjust (f q x) q -- | Remove the value stored at a key. delete :: ByteString -> Trie a -> Trie a {-# INLINE delete #-}-delete q = alterBy (\_ _ _ -> Nothing) q (impossible "delete")+delete = alterBy_ (\_ t -> (Nothing, t)) +-- | Remove all keys beginning with a prefix.+--+-- @since 0.2.6+deleteSubmap :: ByteString -> Trie a -> Trie a+{-# INLINE deleteSubmap #-}+deleteSubmap = alterBy_ (\_ _ -> (Nothing, empty)) + {--------------------------------------------------------------- -- Trie-combining functions ---------------------------------------------------------------} --- | Combine two tries, resolving conflicts by choosing the value--- from the left trie.+-- | Take the union of two tries, resolving conflicts by choosing+-- the value from the left trie. unionL :: Trie a -> Trie a -> Trie a {-# INLINE unionL #-} unionL = mergeBy (\x _ -> Just x) --- | Combine two tries, resolving conflicts by choosing the value--- from the right trie.+-- | Take the union of two tries, resolving conflicts by choosing+-- the value from the right trie. unionR :: Trie a -> Trie a -> Trie a {-# INLINE unionR #-} unionR = mergeBy (\_ y -> Just y)++-- | Take the intersection of two tries, with values from the left trie.+--+-- @since 0.2.6+intersectL :: Trie a -> Trie b -> Trie a+{-# INLINE intersectL #-}+intersectL = intersectBy (\x _ -> Just x)++-- | Take the intersection of two tries, with values from the right trie.+--+-- @since 0.2.6+intersectR :: Trie a -> Trie b -> Trie b+{-# INLINE intersectR #-}+intersectR = intersectBy (\_ y -> Just y) ---------------------------------------------------------------- ----------------------------------------------------------- fin.
− src/Data/Trie/BitTwiddle.hs
@@ -1,171 +0,0 @@-{-# OPTIONS_GHC -Wall -fwarn-tabs -fno-warn-name-shadowing #-}---- The MagicHash is for unboxed primitives (-fglasgow-exts also works)-{-# LANGUAGE CPP, MagicHash #-}--------------------------------------------------------------------- ~ 2021.10.17--- |--- Module : Data.Trie.BitTwiddle--- Copyright : Copyright (c) 2002 Daan Leijen--- License : BSD3--- Maintainer : libraries@haskell.org, wren@cpan.org--- Stability : stable--- Portability : portable (with CPP)------ Functions to treat 'Word' as a bit-vector for big-endian patricia--- trees. This code is duplicated from "Data.IntMap". The only--- differences are that some of the conversion functions are--- specialized to 'Data.Word.Word8' for bytestrings, instead of--- being specialized to 'Int'.-------------------------------------------------------------------module Data.Trie.BitTwiddle- ( Prefix, Mask- , elemToNat- , zero, nomatch- , mask, shorter, branchMask- ) where--import Data.Trie.ByteStringInternal (ByteStringElem)--import Data.Bits--#if __GLASGOW_HASKELL__ >= 503-import GHC.Exts ( Word(..), Int(..), shiftRL# )-#elif __GLASGOW_HASKELL__-import GlaExts ( Word(..), Int(..), shiftRL# )-#else-import Data.Word (Word)-#endif--------------------------------------------------------------------type KeyElem = ByteStringElem-type Prefix = KeyElem-type Mask = KeyElem--elemToNat :: KeyElem -> Word-{-# INLINE elemToNat #-}-elemToNat = fromIntegral--natToElem :: Word -> KeyElem-{-# INLINE natToElem #-}-natToElem = fromIntegral--shiftRL :: Word -> Int -> Word-{-# INLINE shiftRL #-}-#if __GLASGOW_HASKELL__--- GHC: use unboxing to get @shiftRL@ inlined.-shiftRL (W# x) (I# i) = W# (shiftRL# x i)-#else-shiftRL x i = shiftR x i-#endif---{------------------------------------------------------------------ Endian independent bit twiddling (Trie endianness, not architecture)----------------------------------------------------------------}---- TODO: should we use the (Bits Word8) instance instead of 'elemToNat' and (Bits Nat)? We need to compare Core, C--, or ASM in order to decide this. The choice will apply to 'zero', 'mask', 'maskW',... If we shouldn't, then we should probably send a patch upstream to fix the (Bits Word8) instance.---- | Is the value under the mask zero?-zero :: KeyElem -> Mask -> Bool-{-# INLINE zero #-}-zero i m = (elemToNat i) .&. (elemToNat m) == 0---- | Does a value /not/ match some prefix, for all the bits preceding--- a masking bit? (Hence a subtree matching the value doesn't exist.)-nomatch :: KeyElem -> Prefix -> Mask -> Bool-{-# INLINE nomatch #-}-nomatch i p m = mask i m /= p--mask :: KeyElem -> Mask -> Prefix-{-# INLINE mask #-}-mask i m = maskW (elemToNat i) (elemToNat m)---{------------------------------------------------------------------ Big endian operations (Trie endianness, not architecture)----------------------------------------------------------------}---- | Get mask by setting all bits higher than the smallest bit in--- @m@. Then apply that mask to @i@.-maskW :: Word -> Word -> Prefix-{-# INLINE maskW #-}-maskW i m = natToElem (i .&. (complement (m-1) `xor` m))--- TODO: try the alternatives mentioned in the Containers paper:--- \i m -> natToElem (i .&. (negate m - m))--- \i m -> natToElem (i .&. (m * complement 1))--- N.B. these return /all/ the low bits, and therefore they are not equal functions for all m. They are, however, equal when only one bit of m is set.---- | Determine whether the first mask denotes a shorter prefix than--- the second.-shorter :: Mask -> Mask -> Bool-{-# INLINE shorter #-}-shorter m1 m2 = elemToNat m1 > elemToNat m2---- | Determine first differing bit of two prefixes.-branchMask :: Prefix -> Prefix -> Mask-{-# INLINE branchMask #-}-branchMask p1 p2- = natToElem (highestBitMask (elemToNat p1 `xor` elemToNat p2))--{---------------------------------------------------------------- Finding the highest bit (mask) in a word [x] can be done efficiently- in three ways:- * convert to a floating point value and the mantissa tells us the- [log2(x)] that corresponds with the highest bit position. The- mantissa is retrieved either via the standard C function [frexp]- or by some bit twiddling on IEEE compatible numbers (float).- Note that one needs to use at least [double] precision for an- accurate mantissa of 32 bit numbers.- * use bit twiddling, a logarithmic sequence of bitwise or's and- shifts (bit).- * use processor specific assembler instruction (asm).-- The most portable way would be [bit], but is it efficient enough?- I have measured the cycle counts of the different methods on an- AMD Athlon-XP 1800 (~ Pentium III 1.8Ghz) using the RDTSC- instruction:-- highestBitMask: method cycles- --------------- frexp 200- float 33- bit 11- asm 12-- highestBit: method cycles- --------------- frexp 195- float 33- bit 11- asm 11-- Wow, the bit twiddling is on today's RISC like machines even- faster than a single CISC instruction (BSR)!----------------------------------------------------------------}--{---------------------------------------------------------------- [highestBitMask] returns a word where only the highest bit is- set. It is found by first setting all bits in lower positions- than the highest bit and than taking an exclusive or with the- original value. Allthough the function may look expensive, GHC- compiles this into excellent C code that subsequently compiled- into highly efficient machine code. The algorithm is derived from- Jorg Arndt's FXT library.----------------------------------------------------------------}-highestBitMask :: Word -> Word-{-# INLINE highestBitMask #-}-highestBitMask x- = case (x .|. shiftRL x 1) of- x -> case (x .|. shiftRL x 2) of- x -> case (x .|. shiftRL x 4) of- x -> case (x .|. shiftRL x 8) of- x -> case (x .|. shiftRL x 16) of- x -> case (x .|. shiftRL x 32) of -- for 64 bit platforms- x -> (x `xor` shiftRL x 1)------------------------------------------------------------------------------------------------------------------------------ fin.
− src/Data/Trie/ByteStringInternal.hs
@@ -1,112 +0,0 @@-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}---------------------------------------------------------------- ~ 2021.10.17--- |--- Module : Data.Trie.ByteStringInternal--- Copyright : Copyright (c) 2008--2021 wren gayle romano--- License : BSD3--- Maintainer : wren@cpan.org--- Stability : experimental--- Portability : GHC-only------ Helper functions on 'ByteString's for "Data.Trie.Internal".---------------------------------------------------------------module Data.Trie.ByteStringInternal- ( ByteString, ByteStringElem- , breakMaximalPrefix- ) where--import qualified Data.ByteString as S-import Data.ByteString.Internal (ByteString(PS))-import Data.Word-import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)-import Foreign.Ptr (Ptr, plusPtr)-import Foreign.Storable (Storable(..))--- This module name is since @__GLASGOW_HASKELL__ >= 611@.-import GHC.IO (unsafeDupablePerformIO)----------------------------------------------------------------- | Associated type of 'ByteString'-type ByteStringElem = Word8------------------------------------------------------------------ | Returns the longest shared prefix and the two remaining suffixes--- for a pair of strings.------ > s == (\(pre,s',z') -> pre `append` s') (breakMaximalPrefix s z)--- > z == (\(pre,s',z') -> pre `append` z') (breakMaximalPrefix s z)-breakMaximalPrefix- :: ByteString- -> ByteString- -> (ByteString, ByteString, ByteString)-breakMaximalPrefix- str1@(PS s1 off1 len1)- str2@(PS s2 off2 len2)- | len1 == 0 = (S.empty, S.empty, str2)- | len2 == 0 = (S.empty, str1, S.empty)- | otherwise = unsafeDupablePerformIO $- withForeignPtr s1 $ \p1 ->- withForeignPtr s2 $ \p2 -> do- i <- indexOfDifference- (p1 `ptrElemOff` off1)- (p2 `ptrElemOff` off2)- (min len1 len2)- let pre = if off1 + len1 < off2 + len2 -- share the smaller one- then newPS s1 off1 i- else newPS s2 off2 i- let s1' = newPS s1 (off1 + i) (len1 - i)- let s2' = newPS s2 (off2 + i) (len2 - i)-- return $! (,,) !$ pre !$ s1' !$ s2'---- | Get the 'sizeOf' the type, without requiring @-XScopedTypeVariables@--- nor making a spurious call to 'System.IO.Unsafe.unsafePerformIO' or similar.-sizeOfPtr :: Storable a => Ptr a -> Int-sizeOfPtr = sizeOf . (undefined :: Ptr a -> a)-{-# INLINE sizeOfPtr #-}---- | C-style pointer addition, without the excessively liberal type--- of 'plusPtr'.-ptrElemOff :: Storable a => Ptr a -> Int -> Ptr a-ptrElemOff p i = p `plusPtr` (i * sizeOfPtr p)-{-# INLINE ptrElemOff #-}---- | Smart-constructor to share 'S.empty' as appropriate.-newPS :: ForeignPtr ByteStringElem -> Int -> Int -> ByteString-newPS s o l = if l <= 0 then S.empty else PS s o l-{-# INLINE newPS #-}---- | fix associativity bug-(!$) :: (a -> b) -> a -> b-(!$) = ($!)-{-# INLINE (!$) #-}------------------------------------------------------------------ This naive algorithm doesn't depend on architecture details. We--- could speed things up (in theory) by checking a natural word at--- a time and then falling back to checking each byte once the--- mismatched word is found. But in practice that doesn't seem to--- actually speed things up.------ | Calculates the first index where values differ.-indexOfDifference- :: Ptr ByteStringElem- -> Ptr ByteStringElem- -> Int- -> IO Int-indexOfDifference p1 p2 limit = goByte 0- where- goByte n =- if n >= limit- then return limit- else do c1 <- peekElemOff p1 n- c2 <- peekElemOff p2 n- if c1 == c2- then goByte $! n+1- else return n---------------------------------------------------------------------------------------------------------------------- fin.
src/Data/Trie/Convenience.hs view
@@ -1,13 +1,16 @@ {-# OPTIONS_GHC -Wall -fwarn-tabs #-}-+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE Safe #-}+#endif ------------------------------------------------------------------- ~ 2021.10.17+-- ~ 2025-02-11 -- | -- Module : Data.Trie.Convenience--- Copyright : Copyright (c) 2008--2021 wren gayle romano--- License : BSD3+-- Copyright : 2008--2025 wren romano+-- License : BSD-3-Clause -- Maintainer : wren@cpan.org--- Stability : experimental+-- Stability : provisional -- Portability : portable -- -- Additional convenience functions. In order to keep "Data.Trie"@@ -37,16 +40,20 @@ , adjustWithKey , update, updateWithKey - -- * Combining tries ('mergeBy' variants)+ -- * Combining tries ('mergeBy' and 'intersectBy' variants) , disunion , unionWith, unionWith'+ , intersectWith, intersectWith' ) where import Data.Trie-import Data.Trie.Internal (lookupBy_, adjustBy)-import Data.Trie.Errors (impossible)+import Data.Trie.Internal (lookupBy_, alterBy_) import Data.ByteString (ByteString)-import Data.List (foldl', sortBy)+import Data.List (sortBy)+#if !(MIN_VERSION_base(4,20,0))+-- [aka GHC 9.10.1]: 'foldl'' re-exported from "Prelude".+import Data.List (foldl')+#endif import Data.Ord (comparing) ----------------------------------------------------------------@@ -76,15 +83,15 @@ -- currently just an alias for 'fromListR'. fromListR :: [(ByteString,a)] -> Trie a {-# INLINE fromListR #-}-fromListR = fromList -- == foldr (uncurry insert) empty+fromListR = fromList -- ≡ foldr (uncurry insert) empty -- TODO: compare performance against a fromListL variant, adjusting the sort appropriately -- -- | This variant sorts the list before folding over it. This adds--- /O(n log n)/ overhead and requires the whole list be in memory--- at once, but it ensures that the list is in best-case order. The--- benefits generally outweigh the costs.+-- \(\mathcal{O}(n \log n)\) overhead and requires the whole list+-- be in memory at once, but it ensures that the list is in best-case+-- order. The benefits generally outweigh the costs. fromListS :: [(ByteString,a)] -> Trie a {-# INLINE fromListS #-} fromListS = fromListR . sortBy (comparing fst)@@ -109,6 +116,8 @@ -- fusion. If you need list fusion and are running into stack -- overflow problems with 'fromListWith', then this function may -- solve the problem.+--+-- @since 0.2.3 fromListWith' :: (a -> a -> a) -> [(ByteString,a)] -> Trie a {-# INLINE fromListWith' #-} fromListWith' f = foldr (uncurry $ alterBy g') empty@@ -123,6 +132,8 @@ -- the list; the second argument is the \"new\" value from the -- remaining tail of the list (just like the first argument to -- 'foldl'). Thus, @fromListL = fromListWithL const@.+--+-- @since 0.2.3 fromListWithL :: (a -> a -> a) -> [(ByteString,a)] -> Trie a {-# INLINE fromListWithL #-} fromListWithL f = foldl' (flip . uncurry $ alterBy flipG) empty@@ -133,6 +144,8 @@ -- | A variant of 'fromListWithL' which applies the combining -- function strictly.+--+-- @since 0.2.3 fromListWithL' :: (a -> a -> a) -> [(ByteString,a)] -> Trie a {-# INLINE fromListWithL' #-} fromListWithL' f = foldl' (flip . uncurry $ alterBy flipG') empty@@ -143,10 +156,7 @@ ---------------------------------------------------------------- -- | Lookup a key, returning a default value if it's not found. lookupWithDefault :: a -> ByteString -> Trie a -> a-lookupWithDefault def = lookupBy_ f def (const def)- where- f Nothing _ = def- f (Just v) _ = v+lookupWithDefault def = lookupBy_ const (const def) def ---------------------------------------------------------------- @@ -168,6 +178,8 @@ -- | A variant of 'insertWith' which applies the combining function -- strictly.+--+-- @since 0.2.3 insertWith' :: (a -> a -> a) -> ByteString -> a -> Trie a -> Trie a insertWith' f = alterBy $ \_ x mv ->@@ -186,6 +198,8 @@ -- | A variant of 'insertWithKey' which applies the combining -- function strictly.+--+-- @since 0.2.3 insertWithKey' :: (ByteString -> a -> a -> a) -> ByteString -> a -> Trie a -> Trie a insertWithKey' f = alterBy $ \k x mv ->@@ -200,19 +214,15 @@ ---------------------------------------------------------------- -- | Apply a function to change the value at a key. adjustWithKey :: (ByteString -> a -> a) -> ByteString -> Trie a -> Trie a-adjustWithKey f q =- adjustBy (\k _ -> f k) q (impossible "Convenience.adjustWithKey")--- TODO: benchmark vs the definition with alterBy/liftM+adjustWithKey f q = adjust (f q) q -- | Apply a function to the value at a key, possibly removing it. update :: (a -> Maybe a) -> ByteString -> Trie a -> Trie a-update f q =- alterBy (\_ _ mx -> mx >>= f) q (impossible "Convenience.update")+update f q = alterBy_ (\mx t -> (mx >>= f, t)) q -- | A variant of 'update' which also provides the key to the function. updateWithKey :: (ByteString -> a -> Maybe a) -> ByteString -> Trie a -> Trie a-updateWithKey f q =- alterBy (\k _ mx -> mx >>= f k) q (impossible "Convenience.updateWithKey")+updateWithKey f q = alterBy_ (\mx t -> (mx >>= f q, t)) q {- updateLookupWithKey :: (ByteString -> a -> Maybe a) -> ByteString -> Trie a -> (Maybe a, Trie a)@@ -227,18 +237,35 @@ disunion :: Trie a -> Trie a -> Trie a disunion = mergeBy (\_ _ -> Nothing) --- | Combine two tries, using a function to resolve conflicts.+-- | Take the union of two tries, using a function to resolve+-- conflicts. The resulting trie is constructed strictly, but the+-- results of the combining function are evaluated lazily. unionWith :: (a -> a -> a) -> Trie a -> Trie a -> Trie a unionWith f = mergeBy (\x y -> Just (f x y)) --- | A variant of 'unionWith' which applies the combining function+-- | A variant of 'unionWith' which evaluates the combining function -- strictly.+--+-- @since 0.2.3 unionWith' :: (a -> a -> a) -> Trie a -> Trie a -> Trie a unionWith' f = mergeBy (\x y -> Just $! f x y) -{- TODO: (efficiently)-difference, intersection--}+-- | Take the intersection of two tries, with a function to resolve+-- the values. The resulting trie is constructed strictly, bit the+-- results of the combining function are evaluated lazily.+--+-- @since 0.2.6+intersectWith :: (a -> b -> c) -> Trie a -> Trie b -> Trie c+intersectWith f = intersectBy (\x y -> Just (f x y))++-- | A variant of 'intersectWith' which evaluates the combining+-- function strictly.+--+-- @since 0.2.6+intersectWith' :: (a -> b -> c) -> Trie a -> Trie b -> Trie c+intersectWith' f = intersectBy (\x y -> Just $! f x y)++-- TODO(github#23): add 'difference' (efficiently!) ---------------------------------------------------------------- ----------------------------------------------------------- fin.
− src/Data/Trie/Errors.hs
@@ -1,30 +0,0 @@-{-# OPTIONS_GHC -Wall -fwarn-tabs #-}--------------------------------------------------------------------- ~ 2021.10.17--- |--- Module : Data.Trie.Errors--- Copyright : Copyright (c) 2008--2021 wren gayle romano--- License : BSD3--- Maintainer : wren@cpan.org--- Stability : experimental--- Portability : portable------ Internal convenience functions for giving error messages.-------------------------------------------------------------------module Data.Trie.Errors- ( impossible- ) where--------------------------------------------------------------------------------------------------------------------------------------- | The impossible happened. Use this instead of 'undefined' just in case.-impossible :: String -> a-{-# NOINLINE impossible #-}-impossible fn =- error $ "Data.Trie." ++ fn ++ ": the impossible happened. This is a bug, please report it to the maintainer."------------------------------------------------------------------------------------------------------------------------------ fin.
src/Data/Trie/Internal.hs view
@@ -1,1050 +1,2393 @@--- To make GHC stop warning about the Prelude-{-# OPTIONS_GHC -Wall -fwarn-tabs -fno-warn-unused-imports #-}-{-# LANGUAGE NoImplicitPrelude #-}---- For list fusion on toListBy, and guarding `base` versions.-{-# LANGUAGE CPP #-}----------------------------------------------------------------- ~ 2021.10.17--- |--- Module : Data.Trie.Internal--- Copyright : Copyright (c) 2008--2021 wren gayle romano--- License : BSD3--- Maintainer : wren@cpan.org--- Stability : provisional--- Portability : portable (with CPP)------ Internal definition of the 'Trie' data type and generic functions--- for manipulating them. Almost everything here is re-exported--- from "Data.Trie", which is the preferred API for users. This--- module is for developers who need deeper (and potentially fragile)--- access to the abstract type.---------------------------------------------------------------module Data.Trie.Internal- (- -- * Data types- Trie(), showTrie-- -- * Functions for 'ByteString's- , breakMaximalPrefix-- -- * Basic functions- , empty, null, singleton, size-- -- * Conversion and folding functions- , foldrWithKey, toListBy-- -- * Query functions- , lookupBy_, submap- , match_, matches_-- -- * Single-value modification- , alterBy, alterBy_, adjustBy-- -- * Combining tries- , mergeBy-- -- * Mapping functions- , mapBy- , filterMap- , contextualMap- , contextualMap'- , contextualFilterMap- , contextualMapBy-- -- * Priority-queue functions- , minAssoc, maxAssoc- , updateMinViewBy, updateMaxViewBy- ) where--import Prelude hiding (null, lookup)-import qualified Prelude (null, lookup)--import qualified Data.ByteString as S-import Data.Trie.ByteStringInternal-import Data.Trie.BitTwiddle--import Data.Binary (Binary(..), Get, Word8)-#if MIN_VERSION_base(4,9,0)-import Data.Semigroup (Semigroup(..))-#endif-import Data.Monoid (Monoid(..))-import Control.Monad (liftM, liftM3, liftM4)-import Control.Monad (ap)-import Control.Applicative (Applicative(..), (<$>))-import Data.Foldable (Foldable(foldMap))-import Data.Traversable (Traversable(traverse))--#ifdef __GLASGOW_HASKELL__-import GHC.Exts (build)-#endif-----------------------------------------------------------------------------------------------------------------------------{-------------------------------------------------------------- ByteString Big-endian Patricia Trie datatype------------------------------------------------------------}-{--In our idealized representation, we use a (directed) discrete graph-to represent our finite state machine. To organize the set of-outgoing arcs from a given Node we have ArcSet be a big-endian-patricia tree like Data.IntMap. In order to simplify things we then-go through a series of derivations.--data Node a = Accept a (ArcSet a)- | Reject (Branch a) -- Invariant: Must be Branch-data Arc a = Arc ByteString (Node a) -- Invariant: never empty string-data ArcSet a = None- | One {KeyElem} (Arc a)- | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)-data Trie a = Empty- | Start ByteString (Node a) -- Maybe empty string [1]--[1] If we maintain the invariants on how Nodes recurse, then we-can't simply have Start(Node a) because we may have a shared prefix-where the prefix itself is not Accept'ed.----- Squash Arc into One:--- (pure good)-data Node a = Accept a (ArcSet a)- | Reject (Branch a)-data ArcSet a = None- | Arc ByteString (Node a)- | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)-data Trie a = Empty- | Start ByteString (Node a)----- Squash Node together:--- (most likely good)-data Node a = Node (Maybe a) (ArcSet a)-data ArcSet a = None- | Arc ByteString (Node a)- | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)-data Trie a = Empty- | Start ByteString (Node a)----- Squash Empty/None and Arc/Start together:--- (This complicates invariants about non-empty strings and Node's--- recursion, but those can be circumvented by using smart--- constructors.)-data Node a = Node (Maybe a) (ArcSet a)-data Trie a = Empty- | Arc ByteString (Node a)- | Branch {Prefix} {Mask} (Trie a) (Trie a)----- Squash Node into Arc:--- (By this point, pure good)--- Unseen invariants:--- * ByteString non-empty, unless Arc is absolute root of tree--- * If (Maybe a) is Nothing, then (Trie a) is Branch--- * With views, we could re-expand Arc into accepting and--- nonaccepting variants------ [2] Maybe we shouldn't unpack the ByteString. We could specialize--- or inline the breakMaximalPrefix function to prevent constructing--- a new ByteString from the parts...--}--- | A map from 'ByteString's to @a@. For all the generic functions,--- note that tries are strict in the @Maybe@ but not in @a@.------ The 'Monad' instance is strange. If a key @k1@ is a prefix of--- other keys, then results from binding the value at @k1@ will--- override values from longer keys when they collide. If this is--- useful for anything, or if there's a more sensible instance, I'd--- be curious to know.--data Trie a = Empty- | Arc {-# UNPACK #-} !ByteString- !(Maybe a)- !(Trie a)- | Branch {-# UNPACK #-} !Prefix- {-# UNPACK #-} !Mask- !(Trie a)- !(Trie a)- deriving Eq- -- Prefix/Mask should be deterministic regardless of insertion order- -- TODO: prove this is so.----- TODO? add Ord instance like Data.Map?--{-------------------------------------------------------------- Trie instances: serialization et cetera------------------------------------------------------------}---- This instance does not unveil the innards of our abstract type.--- It doesn't emit truly proper Haskell code though, since ByteStrings--- are printed as (ASCII) Strings, but that's not our fault. (Also--- 'fromList' is in "Data.Trie" instead of here.)-instance (Show a) => Show (Trie a) where- showsPrec p t = showParen (p > 10)- $ ("Data.Trie.fromList "++) . shows (toListBy (,) t)----- | Visualization fuction for debugging.-showTrie :: (Show a) => Trie a -> String-showTrie t = shows' id t ""- where- spaces f = map (const ' ') (f "")-- shows' _ Empty = (".\n"++)- shows' ss (Branch p m l r) =- let s' = ("--"++) . shows p . (","++) . shows m . ("-+"++)- ss' = ss . (tail (spaces s') ++)- in s' . shows' (ss' . ("|"++)) l- . ss' . ("|\n"++)- . ss' . ("`"++) . shows' (ss' . (" "++)) r- shows' ss (Arc k mv t') =- let s' = ("--"++) . shows k- . maybe id (\v -> ("-("++) . shows v . (")"++)) mv- . ("--"++)- in s' . shows' (ss . (spaces s' ++)) t'----- TODO?? a Read instance? hrm... should I?---- TODO: consider an instance more like the new one for Data.Map. Better?-instance (Binary a) => Binary (Trie a) where- put Empty = do put (0 :: Word8)- put (Arc k m t) = do put (1 :: Word8); put k; put m; put t- put (Branch p m l r) = do put (2 :: Word8); put p; put m; put l; put r-- get = do tag <- get :: Get Word8- case tag of- 0 -> return Empty- 1 -> liftM3 Arc get get get- _ -> liftM4 Branch get get get get---{-------------------------------------------------------------- Trie instances: Abstract Nonsense------------------------------------------------------------}--instance Functor Trie where- fmap f = go- where- go Empty = Empty- go (Arc k Nothing t) = Arc k Nothing (go t)- go (Arc k (Just v) t) = Arc k (Just (f v)) (go t)- go (Branch p m l r) = Branch p m (go l) (go r)---instance Foldable Trie where- -- If our definition of foldr is so much faster than the Endo- -- default, then maybe we should remove this and use the default- -- foldMap based on foldr- foldMap f = go- where- go Empty = mempty- go (Arc _ Nothing t) = go t- go (Arc _ (Just v) t) = f v `mappend` go t- go (Branch _ _ l r) = go l `mappend` go r-- {- This definition is much faster, but it's also wrong- -- (or at least different than foldrWithKey)- foldr f = \z t -> go t id z- where- go Empty k x = k x- go (Branch _ _ l r) k x = go r (go l k) x- go (Arc _ Nothing t) k x = go t k x- go (Arc _ (Just v) t) k x = go t k (f v x)-- foldl f = \z t -> go t id z- where- go Empty k x = k x- go (Branch _ _ l r) k x = go l (go r k) x- go (Arc _ Nothing t) k x = go t k x- go (Arc _ (Just v) t) k x = go t k (f x v)- -}---- TODO: newtype Keys = K Trie ; instance Foldable Keys--- TODO: newtype Assoc = A Trie ; instance Foldable Assoc--instance Traversable Trie where- traverse f = go- where- go Empty = pure Empty- go (Arc k Nothing t) = Arc k Nothing <$> go t- go (Arc k (Just v) t) = Arc k . Just <$> f v <*> go t- go (Branch p m l r) = Branch p m <$> go l <*> go r--instance Applicative Trie where- pure = return- (<*>) = ap---- Does this even make sense? It's not nondeterminism like lists--- and sets. If no keys were prefixes of other keys it'd make sense--- as a decision-tree; but since keys /can/ prefix, tries formed--- from shorter keys can shadow the results from longer keys due--- to the 'unionL'. It does seem to follow the laws though... What--- computation could this possibly represent?------ 1. return x >>= f == f x--- 2. m >>= return == m--- 3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)-instance Monad Trie where- return = singleton S.empty-- (>>=) Empty _ = empty- (>>=) (Branch p m l r) f = branch p m (l >>= f) (r >>= f)- (>>=) (Arc k Nothing t) f = arc k Nothing (t >>= f)- (>>=) (Arc k (Just v) t) f = arc k Nothing (f v `unionL` (t >>= f))- where- unionL = mergeBy (\x _ -> Just x)---#if MIN_VERSION_base(4,9,0)--- The "Data.Semigroup" module is in base since 4.9.0.0; but having--- the 'Semigroup' superclass for the 'Monoid' instance only comes--- into force in base 4.11.0.0.-instance (Semigroup a) => Semigroup (Trie a) where- (<>) = mergeBy $ \x y -> Just (x <> y)- -- TODO: optimized implementations of:- -- sconcat :: NonEmpty a -> a- -- stimes :: Integral b => b -> a -> a-#endif---- This instance is more sensible than Data.IntMap and Data.Map's-instance (Monoid a) => Monoid (Trie a) where- mempty = empty- mappend = mergeBy $ \x y -> Just (x `mappend` y)----- Since the Monoid instance isn't natural in @a@, I can't think--- of any other sensible instance for MonadPlus. It's as specious--- as Maybe, IO, and STM's instances though.------ MonadPlus laws: <http://www.haskell.org/haskellwiki/MonadPlus>--- 1. <Trie a, mzero, mplus> forms a monoid--- 2. mzero >>= f === mzero--- 3. m >> mzero === mzero--- 4. mplus m n >>= k === mplus (m >>= k) (n >>= k)--- 4' mplus (return a) n === return a-{---- Follows #1, #1, and #3. But it does something like 4' instead--- of actually doing #4 (since we'd merge the trees generated by--- @k@ for conflicting values)------ TODO: cf Control.Applicative.Alternative (base-4, but not Hugs).--- But (<*>) gets odd when the function is not 'pure'... maybe--- helpful though.-instance MonadPlus Trie where- mzero = empty- mplus = unionL where unionL = mergeBy (\x _ -> Just x)--}---{-------------------------------------------------------------- Extra mapping functions------------------------------------------------------------}---- | Apply a function to all values, potentially removing them.-filterMap :: (a -> Maybe b) -> Trie a -> Trie b-filterMap f = go- where- go Empty = empty- go (Arc k Nothing t) = arc k Nothing (go t)- go (Arc k (Just v) t) = arc k (f v) (go t)- go (Branch p m l r) = branch p m (go l) (go r)----- | Generic version of 'fmap'. This function is notably more--- expensive than 'fmap' or 'filterMap' because we have to reconstruct--- the keys.-mapBy :: (ByteString -> a -> Maybe b) -> Trie a -> Trie b-mapBy f = go S.empty- where- go _ Empty = empty- go q (Arc k Nothing t) = arc k Nothing (go q' t) where q' = S.append q k- go q (Arc k (Just v) t) = arc k (f q' v) (go q' t) where q' = S.append q k- go q (Branch p m l r) = branch p m (go q l) (go q r)----- | A variant of 'fmap' which provides access to the subtrie rooted--- at each value.-contextualMap :: (a -> Trie a -> b) -> Trie a -> Trie b-contextualMap f = go- where- go Empty = Empty- go (Arc k Nothing t) = Arc k Nothing (go t)- go (Arc k (Just v) t) = Arc k (Just (f v t)) (go t)- go (Branch p m l r) = Branch p m (go l) (go r)----- | A variant of 'contextualMap' which applies the function strictly.-contextualMap' :: (a -> Trie a -> b) -> Trie a -> Trie b-contextualMap' f = go- where- go Empty = Empty- go (Arc k Nothing t) = Arc k Nothing (go t)- go (Arc k (Just v) t) = Arc k (Just $! f v t) (go t)- go (Branch p m l r) = Branch p m (go l) (go r)----- | A contextual variant of 'filterMap'.-contextualFilterMap :: (a -> Trie a -> Maybe b) -> Trie a -> Trie b-contextualFilterMap f = go- where- go Empty = empty- go (Arc k Nothing t) = arc k Nothing (go t)- go (Arc k (Just v) t) = arc k (f v t) (go t)- go (Branch p m l r) = branch p m (go l) (go r)----- | A contextual variant of 'mapBy'. Again note that this is--- expensive since we must reconstruct the keys.-contextualMapBy :: (ByteString -> a -> Trie a -> Maybe b) -> Trie a -> Trie b-contextualMapBy f = go S.empty- where- go _ Empty = empty- go q (Arc k Nothing t) = arc k Nothing (go (S.append q k) t)- go q (Arc k (Just v) t) = let q' = S.append q k- in arc k (f q' v t) (go q' t)- go q (Branch p m l r) = branch p m (go q l) (go q r)---{-------------------------------------------------------------- Smart constructors and helper functions for building tries------------------------------------------------------------}---- | Smart constructor to prune @Empty@ from @Branch@es.-branch :: Prefix -> Mask -> Trie a -> Trie a -> Trie a-{-# INLINE branch #-}-branch _ _ Empty r = r-branch _ _ l Empty = l-branch p m l r = Branch p m l r----- | Smart constructor to prune @Arc@s that lead nowhere.--- N.B if mv=Just then doesn't check whether t=epsilon. It's up to callers to ensure that invariant isn't broken.-arc :: ByteString -> Maybe a -> Trie a -> Trie a-{-# INLINE arc #-}-arc k mv@(Just _) t = Arc k mv t-arc _ Nothing Empty = Empty-arc k Nothing t@(Branch _ _ _ _) | S.null k = t- | otherwise = Arc k Nothing t-arc k Nothing (Arc k' mv' t') = Arc (S.append k k') mv' t'----- | Smart constructor to join two tries into a @Branch@ with maximal--- prefix sharing. Requires knowing the prefixes, but can combine--- either @Branch@es or @Arc@s.------ N.B. /do not/ use if prefixes could match entirely!-branchMerge :: Prefix -> Trie a -> Prefix -> Trie a -> Trie a-{-# INLINE branchMerge #-}-branchMerge _ Empty _ t2 = t2-branchMerge _ t1 _ Empty = t1-branchMerge p1 t1 p2 t2- | zero p1 m = Branch p m t1 t2- | otherwise = Branch p m t2 t1- where- m = branchMask p1 p2- p = mask p1 m----- It would be better if Arc used--- Data.ByteString.TrieInternal.wordHead somehow, that way--- we can see 4/8/?*Word8 at a time instead of just one.--- But that makes maintaining invariants ...difficult :(-getPrefix :: Trie a -> Prefix-{-# INLINE getPrefix #-}-getPrefix (Branch p _ _ _) = p-getPrefix (Arc k _ _) | S.null k = 0 -- for lack of a better value- | otherwise = S.head k-getPrefix Empty = error "getPrefix: no Prefix of Empty"---{-------------------------------------------------------------- Error messages------------------------------------------------------------}---- TODO: shouldn't we inline the logic and just NOINLINE the string constant? There are only three use sites, which themselves aren't inlined...-errorLogHead :: String -> ByteString -> ByteStringElem-{-# NOINLINE errorLogHead #-}-errorLogHead fn q- | S.null q = error $ "Data.Trie.Internal." ++ fn ++": found null subquery"- | otherwise = S.head q------------------------------------------------------------------------------------------------------------------------------{-------------------------------------------------------------- Basic functions------------------------------------------------------------}---- | /O(1)/, Construct the empty trie.-empty :: Trie a-{-# INLINE empty #-}-empty = Empty----- | /O(1)/, Is the trie empty?-null :: Trie a -> Bool-{-# INLINE null #-}-null Empty = True-null _ = False----- | /O(1)/, Construct a singleton trie.-singleton :: ByteString -> a -> Trie a-{-# INLINE singleton #-}-singleton k v = Arc k (Just v) Empty--- For singletons, don't need to verify invariant on arc length >0----- | /O(n)/, Get count of elements in trie.-size :: Trie a -> Int-{-# INLINE size #-}-size t = size' t id 0---- | /O(n)/, CPS accumulator helper for calculating 'size'.-size' :: Trie a -> (Int -> Int) -> Int -> Int-size' Empty f n = f n-size' (Branch _ _ l r) f n = size' l (size' r f) n-size' (Arc _ Nothing t) f n = size' t f n-size' (Arc _ (Just _) t) f n = size' t f $! n + 1---{-------------------------------------------------------------- Conversion functions------------------------------------------------------------}---- Still rather inefficient------ TODO: rewrite list-catenation to be lazier (real CPS instead of--- function building? is the function building really better than--- (++) anyways?)--- N.B. If our manual definition of foldr/foldl (using function--- application) is so much faster than the default Endo definition--- (using function composition), then we should make this use--- application instead too.------ TODO: the @q@ accumulator should be lazy ByteString and only--- forced by @fcons@. It's already non-strict, but we should ensure--- O(n) not O(n^2) when it's forced.------ BUG: not safe for deep strict @fcons@, only for WHNF-strict like (:)--- Where to put the strictness to amortize it?------ | Convert a trie into a list (in key-sorted order) using a--- function, folding the list as we go.-foldrWithKey :: (ByteString -> a -> b -> b) -> b -> Trie a -> b-foldrWithKey fcons nil = \t -> go S.empty t nil- where- go _ Empty = id- go q (Branch _ _ l r) = go q l . go q r- go q (Arc k mv t) =- case mv of- Nothing -> rest- Just v -> fcons k' v . rest- where- rest = go k' t- k' = S.append q k----- cf Data.ByteString.unpack--- <http://hackage.haskell.org/packages/archive/bytestring/0.9.1.4/doc/html/src/Data-ByteString.html>------ | Convert a trie into a list using a function. Resulting values--- are in key-sorted order.-toListBy :: (ByteString -> a -> b) -> Trie a -> [b]-{-# INLINE toListBy #-}-#if !defined(__GLASGOW_HASKELL__)--- TODO: should probably inline foldrWithKey--- TODO: compare performance of that vs both this and the GHC version-toListBy f t = foldrWithKey (((:) .) . f) [] t-#else--- Written with 'build' to enable the build\/foldr fusion rules.-toListBy f t = build (toListByFB f t)---- TODO: should probably have a specialized version for strictness,--- and a rule to rewrite generic lazy version into it. As per--- Data.ByteString.unpack and the comments there about strictness--- and fusion.-toListByFB :: (ByteString -> a -> b) -> Trie a -> (b -> c -> c) -> c -> c-{-# INLINE [0] toListByFB #-}-toListByFB f t cons nil = foldrWithKey ((cons .) . f) nil t-#endif---{-------------------------------------------------------------- Query functions (just recurse)------------------------------------------------------------}---- | Generic function to find a value (if it exists) and the subtrie--- rooted at the prefix. The first function argument is called if and--- only if a node is exactly reachable by the query; if no node is--- exactly reachable the default value is used; if the middle of--- an arc is reached, the second function argument is used.------ This function is intended for internal use. For the public-facing--- version, see @lookupBy@ in "Data.Trie".-lookupBy_ :: (Maybe a -> Trie a -> b) -> b -> (Trie a -> b)- -> ByteString -> Trie a -> b-lookupBy_ f z a = lookupBy_'- where- -- | Deal with epsilon query (when there is no epsilon value)- lookupBy_' q t@(Branch _ _ _ _) | S.null q = f Nothing t- lookupBy_' q t = go q t-- -- | The main recursion- go _ Empty = z-- go q (Arc k mv t) =- let (_,k',q') = breakMaximalPrefix k q- in case (not $ S.null k', S.null q') of- (True, True) -> a (Arc k' mv t)- (True, False) -> z- (False, True) -> f mv t- (False, False) -> go q' t-- go q t_@(Branch _ _ _ _) = findArc t_- where- qh = errorLogHead "lookupBy_" q-- -- | /O(min(m,W))/, where /m/ is number of @Arc@s in this- -- branching, and /W/ is the word size of the Prefix,Mask type.- findArc (Branch p m l r)- | nomatch qh p m = z- | zero qh m = findArc l- | otherwise = findArc r- findArc t@(Arc _ _ _) = go q t- findArc Empty = z----- This function needs to be here, not in "Data.Trie", because of--- 'arc' which isn't exported. We could use the monad instance--- instead, though it'd be far more circuitous.--- arc k Nothing t === singleton k () >> t--- arc k (Just v) t === singleton k v >>= unionR t . singleton S.empty--- (...except 'arc' doesn't do the invariant correction--- of (>>=) for epsilon`elem`t)------ | Return the subtrie containing all keys beginning with a prefix.-submap :: ByteString -> Trie a -> Trie a-{-# INLINE submap #-}-submap q = lookupBy_ (arc q) empty (arc q Nothing) q-{- -- Disable superfluous error checking.- -- @submap'@ would replace the first argument to @lookupBy_@- where- submap' Nothing Empty = errorEmptyAfterNothing "submap"- submap' Nothing (Arc _ _ _) = errorArcAfterNothing "submap"- submap' mx t = Arc q mx t--errorInvariantBroken :: String -> String -> a-{-# NOINLINE errorInvariantBroken #-}-errorInvariantBroken s e = error (s ++ ": Invariant was broken" ++ e')- where- e' = if Prelude.null e then e else ", found: " ++ e--errorArcAfterNothing :: String -> a-{-# NOINLINE errorArcAfterNothing #-}-errorArcAfterNothing s = errorInvariantBroken s "Arc after Nothing"--errorEmptyAfterNothing :: String -> a-{-# NOINLINE errorEmptyAfterNothing #-}-errorEmptyAfterNothing s = errorInvariantBroken s "Empty after Nothing"--- -}------ TODO: would it be worth it to have a variant like 'lookupBy_' which takes the three continuations?---- | Given a query, find the longest prefix with an associated value--- in the trie, returning the length of that prefix and the associated--- value.------ This function may not have the most useful return type. For a--- version that returns the prefix itself as well as the remaining--- string, see @match@ in "Data.Trie".-match_ :: Trie a -> ByteString -> Maybe (Int, a)-match_ = flip start- where- -- | Deal with epsilon query (when there is no epsilon value)- start q (Branch _ _ _ _) | S.null q = Nothing- start q t = goNothing 0 q t-- -- | The initial recursion- goNothing _ _ Empty = Nothing-- goNothing n q (Arc k mv t) =- let (p,k',q') = breakMaximalPrefix k q- n' = n + S.length p- in n' `seq`- if S.null k'- then- if S.null q'- then (,) n' <$> mv- else- case mv of- Nothing -> goNothing n' q' t- Just v -> goJust n' v n' q' t- else Nothing-- goNothing n q t_@(Branch _ _ _ _) = findArc t_- where- qh = errorLogHead "match_" q-- -- | /O(min(m,W))/, where /m/ is number of @Arc@s in this- -- branching, and /W/ is the word size of the Prefix,Mask type.- findArc (Branch p m l r)- | nomatch qh p m = Nothing- | zero qh m = findArc l- | otherwise = findArc r- findArc t@(Arc _ _ _) = goNothing n q t- findArc Empty = Nothing-- -- | The main recursion- goJust n0 v0 _ _ Empty = Just (n0,v0)-- goJust n0 v0 n q (Arc k mv t) =- let (p,k',q') = breakMaximalPrefix k q- n' = n + S.length p- in n' `seq`- if S.null k'- then- if S.null q'- then- case mv of- Nothing -> Just (n0,v0)- Just v -> Just (n',v)- else- case mv of- Nothing -> goJust n0 v0 n' q' t- Just v -> goJust n' v n' q' t- else Just (n0,v0)-- goJust n0 v0 n q t_@(Branch _ _ _ _) = findArc t_- where- qh = errorLogHead "match_" q-- -- | /O(min(m,W))/, where /m/ is number of @Arc@s in this- -- branching, and /W/ is the word size of the Prefix,Mask type.- findArc (Branch p m l r)- | nomatch qh p m = Just (n0,v0)- | zero qh m = findArc l- | otherwise = findArc r- findArc t@(Arc _ _ _) = goJust n0 v0 n q t- findArc Empty = Just (n0,v0)----- | Given a query, find all prefixes with associated values in the--- trie, returning their lengths and values. This function is a--- good producer for list fusion.------ This function may not have the most useful return type. For a--- version that returns the prefix itself as well as the remaining--- string, see @matches@ in "Data.Trie".-matches_ :: Trie a -> ByteString -> [(Int,a)]-matches_ t q =-#if !defined(__GLASGOW_HASKELL__)- matchFB_ t q (((:) .) . (,)) []-#else- build (\cons nil -> matchFB_ t q ((cons .) . (,)) nil)-{-# INLINE matches_ #-}-#endif--matchFB_ :: Trie a -> ByteString -> (Int -> a -> r -> r) -> r -> r-matchFB_ = \t q cons nil -> matchFB_' cons q t nil- where- matchFB_' cons = start- where- -- | Deal with epsilon query (when there is no epsilon value)- start q (Branch _ _ _ _) | S.null q = id- start q t = go 0 q t-- -- | The main recursion- go _ _ Empty = id-- go n q (Arc k mv t) =- let (p,k',q') = breakMaximalPrefix k q- n' = n + S.length p- in n' `seq`- if S.null k'- then- case mv of { Nothing -> id; Just v -> cons n' v}- .- if S.null q' then id else go n' q' t- else id-- go n q t_@(Branch _ _ _ _) = findArc t_- where- qh = errorLogHead "matches_" q-- -- | /O(min(m,W))/, where /m/ is number of @Arc@s in this- -- branching, and /W/ is the word size of the Prefix,Mask type.- findArc (Branch p m l r)- | nomatch qh p m = id- | zero qh m = findArc l- | otherwise = findArc r- findArc t@(Arc _ _ _) = go n q t- findArc Empty = id---{-------------------------------------------------------------- Single-value modification functions (recurse and clone spine)------------------------------------------------------------}---- TODO: We should CPS on Empty to avoid cloning spine if no change.--- Difficulties arise with the calls to 'branch' and 'arc'. Will--- have to create a continuation chain, so no savings on memory--- allocation; but would have savings on held memory, if they're--- still holding the old one...------ | Generic function to alter a trie by one element with a function--- to resolve conflicts (or non-conflicts).-alterBy :: (ByteString -> a -> Maybe a -> Maybe a)- -> ByteString -> a -> Trie a -> Trie a-alterBy f = alterBy_ (\k v mv t -> (f k v mv, t))--- TODO: use GHC's 'inline' function so that this gets specialized away.--- TODO: benchmark to be sure that this doesn't introduce unforseen performance costs because of the uncurrying etc.----- | A variant of 'alterBy' which also allows modifying the sub-trie.-alterBy_ :: (ByteString -> a -> Maybe a -> Trie a -> (Maybe a, Trie a))- -> ByteString -> a -> Trie a -> Trie a-alterBy_ f_ q_ x_- | S.null q_ = alterEpsilon- | otherwise = go q_- where- f = f_ q_ x_- nothing q = uncurry (arc q) (f Nothing Empty)-- alterEpsilon t_@Empty = uncurry (arc q_) (f Nothing t_)- alterEpsilon t_@(Branch _ _ _ _) = uncurry (arc q_) (f Nothing t_)- alterEpsilon t_@(Arc k mv t) | S.null k = uncurry (arc q_) (f mv t)- | otherwise = uncurry (arc q_) (f Nothing t_)--- go q Empty = nothing q-- go q t@(Branch p m l r)- | nomatch qh p m = branchMerge p t qh (nothing q)- | zero qh m = branch p m (go q l) r- | otherwise = branch p m l (go q r)- where- qh = errorLogHead "alterBy" q-- go q t_@(Arc k mv t) =- let (p,k',q') = breakMaximalPrefix k q in- case (not $ S.null k', S.null q') of- (True, True) -> -- add node to middle of arc- uncurry (arc p) (f Nothing (Arc k' mv t))- (True, False) ->- case nothing q' of- Empty -> t_ -- Nothing to add, reuse old arc- l -> arc' (branchMerge (getPrefix l) l (getPrefix r) r)- where- r = Arc k' mv t-- -- inlined version of 'arc'- arc' | S.null p = id- | otherwise = Arc p Nothing-- (False, True) -> uncurry (arc k) (f mv t)- (False, False) -> arc k mv (go q' t)----- | Alter the value associated with a given key. If the key is not--- present, then the trie is returned unaltered. See 'alterBy' if--- you are interested in inserting new keys or deleting old keys.--- Because this function does not need to worry about changing the--- trie structure, it is somewhat faster than 'alterBy'.-adjustBy :: (ByteString -> a -> a -> a)- -> ByteString -> a -> Trie a -> Trie a-adjustBy f_ q_ x_- | S.null q_ = adjustEpsilon- | otherwise = go q_- where- f = f_ q_ x_-- adjustEpsilon (Arc k (Just v) t) | S.null k = Arc k (Just (f v)) t- adjustEpsilon t_ = t_-- go _ Empty = Empty-- go q t@(Branch p m l r)- | nomatch qh p m = t- | zero qh m = Branch p m (go q l) r- | otherwise = Branch p m l (go q r)- where- qh = errorLogHead "adjustBy" q-- go q t_@(Arc k mv t) =- let (_,k',q') = breakMaximalPrefix k q in- case (not $ S.null k', S.null q') of- (True, True) -> t_ -- don't break arc inline- (True, False) -> t_ -- don't break arc branching- (False, True) -> Arc k (liftM f mv) t- (False, False) -> Arc k mv (go q' t)---{-------------------------------------------------------------- Trie-combining functions------------------------------------------------------------}---- TEST CASES: foldr (unionL . uncurry singleton) empty t--- foldr (uncurry insert) empty t--- where t = map (\s -> (pk s, 0))--- ["heat","hello","hoi","apple","appa","hell","appb","appc"]------ | Combine two tries, using a function to resolve collisions.--- This can only define the space of functions between union and--- symmetric difference but, with those two, all set operations can--- be defined (albeit inefficiently).-mergeBy :: (a -> a -> Maybe a) -> Trie a -> Trie a -> Trie a-mergeBy f = mergeBy'- where- -- | Deals with epsilon entries, before recursing into @go@- mergeBy'- t0_@(Arc k0 mv0 t0)- t1_@(Arc k1 mv1 t1)- | S.null k0 && S.null k1 = arc k0 (mergeMaybe f mv0 mv1) (go t0 t1)- | S.null k0 = arc k0 mv0 (go t0 t1_)- | S.null k1 = arc k1 mv1 (go t1 t0_)- mergeBy'- (Arc k0 mv0@(Just _) t0)- t1_@(Branch _ _ _ _)- | S.null k0 = arc k0 mv0 (go t0 t1_)- mergeBy'- t0_@(Branch _ _ _ _)- (Arc k1 mv1@(Just _) t1)- | S.null k1 = arc k1 mv1 (go t1 t0_)- mergeBy' t0_ t1_ = go t0_ t1_--- -- | The main recursion- go Empty t1 = t1- go t0 Empty = t0-- -- /O(n+m)/ for this part where /n/ and /m/ are sizes of the branchings- go t0@(Branch p0 m0 l0 r0)- t1@(Branch p1 m1 l1 r1)- | shorter m0 m1 = union0- | shorter m1 m0 = union1- | p0 == p1 = branch p0 m0 (go l0 l1) (go r0 r1)- | otherwise = branchMerge p0 t0 p1 t1- where- union0 | nomatch p1 p0 m0 = branchMerge p0 t0 p1 t1- | zero p1 m0 = branch p0 m0 (go l0 t1) r0- | otherwise = branch p0 m0 l0 (go r0 t1)-- union1 | nomatch p0 p1 m1 = branchMerge p0 t0 p1 t1- | zero p0 m1 = branch p1 m1 (go t0 l1) r1- | otherwise = branch p1 m1 l1 (go t0 r1)-- -- We combine these branches of 'go' in order to clarify where the definitions of 'p0', 'p1', 'm'', 'p'' are relevant. However, this may introduce inefficiency in the pattern matching automaton...- -- TODO: check. And get rid of 'go'' if it does.- go t0_ t1_ = go' t0_ t1_- where- p0 = getPrefix t0_- p1 = getPrefix t1_- m' = branchMask p0 p1- p' = mask p0 m'-- go' (Arc k0 mv0 t0)- (Arc k1 mv1 t1)- | m' == 0 =- let (pre,k0',k1') = breakMaximalPrefix k0 k1 in- if S.null pre- then error "mergeBy: no mask, but no prefix string"- else let {-# INLINE arcMerge #-}- arcMerge mv' t1' t2' = arc pre mv' (go t1' t2')- in case (S.null k0', S.null k1') of- (True, True) -> arcMerge (mergeMaybe f mv0 mv1) t0 t1- (True, False) -> arcMerge mv0 t0 (Arc k1' mv1 t1)- (False,True) -> arcMerge mv1 (Arc k0' mv0 t0) t1- (False,False) -> arcMerge Nothing (Arc k0' mv0 t0)- (Arc k1' mv1 t1)- go' (Arc _ _ _)- (Branch _p1 m1 l r)- | nomatch p0 p1 m1 = branchMerge p1 t1_ p0 t0_- | zero p0 m1 = branch p1 m1 (go t0_ l) r- | otherwise = branch p1 m1 l (go t0_ r)- go' (Branch _p0 m0 l r)- (Arc _ _ _)- | nomatch p1 p0 m0 = branchMerge p0 t0_ p1 t1_- | zero p1 m0 = branch p0 m0 (go l t1_) r- | otherwise = branch p0 m0 l (go r t1_)-- -- Inlined branchMerge. Both tries are disjoint @Arc@s now.- go' _ _ | zero p0 m' = Branch p' m' t0_ t1_- go' _ _ = Branch p' m' t1_ t0_---mergeMaybe :: (a -> a -> Maybe a) -> Maybe a -> Maybe a -> Maybe a-{-# INLINE mergeMaybe #-}-mergeMaybe _ Nothing Nothing = Nothing-mergeMaybe _ Nothing mv1@(Just _) = mv1-mergeMaybe _ mv0@(Just _) Nothing = mv0-mergeMaybe f (Just v0) (Just v1) = f v0 v1---{-------------------------------------------------------------- Priority-queue functions------------------------------------------------------------}---- | Return the lexicographically smallest 'ByteString' and the--- value it's mapped to; or 'Nothing' for the empty trie. When one--- entry is a prefix of another, the prefix will be returned.-minAssoc :: Trie a -> Maybe (ByteString, a)-minAssoc = go S.empty- where- go _ Empty = Nothing- go q (Arc k (Just v) _) = Just (S.append q k,v)- go q (Arc k Nothing t) = go (S.append q k) t- go q (Branch _ _ l _) = go q l----- | Return the lexicographically largest 'ByteString' and the--- value it's mapped to; or 'Nothing' for the empty trie. When one--- entry is a prefix of another, the longer one will be returned.-maxAssoc :: Trie a -> Maybe (ByteString, a)-maxAssoc = go S.empty- where- go _ Empty = Nothing- go q (Arc k (Just v) Empty) = Just (S.append q k,v)- go q (Arc k _ t) = go (S.append q k) t- go q (Branch _ _ _ r) = go q r---mapView :: (Trie a -> Trie a)- -> Maybe (ByteString, a, Trie a) -> Maybe (ByteString, a, Trie a)-mapView _ Nothing = Nothing-mapView f (Just (k,v,t)) = Just (k,v, f t)----- | Update the 'minAssoc' and return the old 'minAssoc'.-updateMinViewBy :: (ByteString -> a -> Maybe a)- -> Trie a -> Maybe (ByteString, a, Trie a)-updateMinViewBy f = go S.empty- where- go _ Empty = Nothing- go q (Arc k (Just v) t) = let q' = S.append q k- in Just (q',v, arc k (f q' v) t)- go q (Arc k Nothing t) = mapView (arc k Nothing) (go (S.append q k) t)- go q (Branch p m l r) = mapView (\l' -> branch p m l' r) (go q l)----- | Update the 'maxAssoc' and return the old 'maxAssoc'.-updateMaxViewBy :: (ByteString -> a -> Maybe a)- -> Trie a -> Maybe (ByteString, a, Trie a)-updateMaxViewBy f = go S.empty- where- go _ Empty = Nothing- go q (Arc k (Just v) Empty) = let q' = S.append q k- in Just (q',v, arc k (f q' v) Empty)- go q (Arc k mv t) = mapView (arc k mv) (go (S.append q k) t)- go q (Branch p m l r) = mapView (branch p m l) (go q r)+-- Not using -Wcompat, because it wants outdated things for GHC 8.0/8.2+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+-- Note: can also pass show-extensions to get Haddock to show+-- everything implicit/entailed by the things we actually request.+-- Apparently we're getting: MonoLocalBinds, KindSignatures,+-- ExplicitNamespaces; regardless of whether we set the default to Haskell98 or Haskell2010+{-# OPTIONS_HADDOCK not-home #-}++{-# LANGUAGE NoImplicitPrelude, CPP, BangPatterns #-}+#if __GLASGOW_HASKELL__ >= 708+-- For defining the 'IsList' instance.+{-# LANGUAGE TypeFamilies #-}+#endif+#if __GLASGOW_HASKELL__ >= 701+-- Alas, "GHC.Exts" isn't considered safe, even though 'build' and+-- 'IsList'(..) surely are.+{-# LANGUAGE Trustworthy #-}+#endif+------------------------------------------------------------+-- ~ 2025-02-11+-- |+-- Module : Data.Trie.Internal+-- Copyright : 2008--2025 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : experimental+-- Portability : portable (with CPP)+--+-- Internal definition of the 'Trie' data type and generic functions+-- for manipulating them. Almost everything here is re-exported+-- from "Data.Trie", which is the preferred API for users. This+-- module is for developers who need deeper (and less stable) access+-- to the abstract type.+--+-- @since 0.1.3+------------------------------------------------------------++module Data.Trie.Internal+ (+ -- * Data types+ Trie()+ -- BUG: can't seem to put this at the top: it'll gobble up the+ -- following section name and replace it. (I'm guessing that's+ -- something to do with the section not having any exported+ -- entities?)+ -- *** Performance Warning+ -- $bug25++ -- * Basic functions+ , empty, null, singleton, size++ -- * List-conversion functions+ , fromList+ , toList, toListBy, elems++ -- * Query functions+ , lookupBy_, submap+ , match_, matches_++ -- * Simple modification+ , alterBy, alterBy_, adjust++ -- * Combining tries+ , wip_unionWith+ , mergeBy, intersectBy++ -- * Priority-queue functions+ , minAssoc, maxAssoc+ , updateMinViewBy, updateMaxViewBy++ -- * Mapping, filtering, folding, and traversing+ -- ** Filterable+ , filter+ , filterMap+ , mapBy+ -- ** Witherable+ , filterA+ , wither+ -- TODO: 'witherBy' (effectful 'mapBy')+ -- ** Contextual filtering\/mapping functions+ , contextualMap+ , contextualMap'+ , contextualFilterMap+ , contextualMapBy+ -- TODO: 'contextualWither'+ -- TODO: 'contextualWitherBy' (effectful 'contextualMapBy')+ -- ** Foldable+ , foldrWithKey, foldrWithKey', foldlWithKey, foldlWithKey'+ , cata_, cata+ -- ** Traverse+ , traverseWithKey++ -- * Internal utility functions+ , showTrie+ , breakMaximalPrefix+ ) where++import Prelude hiding (null, lookup, filter)++import qualified Data.ByteString as S+import qualified Data.ByteString.Unsafe as SU+import Data.Trie.Internal.ByteString+import Data.Trie.Internal.BitTwiddle+import Data.Trie.Internal.Errors (impossible)++import Data.Binary (Binary(..), Get, Word8)+import Data.Bits (xor)++#if MIN_VERSION_base(4,9,0)+-- [aka GHC 8.0.1]: "Data.Semigroup" added to base.+--+-- Note: [base-4.13.0 / GHC 8.8.1] has Prelude re-export 'Semigroup'+-- (the class name) and 'Data.Semigroup.<>'; however it does not+-- re-export 'stimes' nor (I assume) 'sconcat'!+import Data.Semigroup (Semigroup(..))+#endif+#if !(MIN_VERSION_base(4,8,0))+-- [aka GHC 7.10.1]: Prelude re-exports 'Monoid'.+import Data.Monoid (Monoid(..))+#endif++import Control.DeepSeq (NFData(rnf))+import Control.Monad (liftM3, liftM4)++import qualified Data.Foldable as F+-- [base-4.10.0.0 / GHC 8.2.1] moved 'liftA2' into the 'Applicative'+-- class for performance reasons; so we want to use it wherever+-- possible, for those same performance reasons. However, while+-- the Prelude re-exports all the other methods of 'Applicative'+-- since base-4.8, it does not re-export 'liftA2' (at least not up+-- through base-4.16.0.0 / GHC 9.2.1).+#if !(MIN_VERSION_base(4,18,0))+-- [base-4.18 / GHC 9.6.1]: Prelude now re-exports 'liftA2'.+import Control.Applicative (liftA2)+#endif+#if MIN_VERSION_base(4,8,0)+-- [aka GHC 7.10.1]: Prelude re-exports 'Applicative', @(<$>)@,+-- 'Foldable', and 'Traversable'. But not 'liftA2'.+#else+import Control.Applicative (Applicative(..), (<$>))+import Data.Foldable (Foldable())+import Data.Traversable (Traversable(traverse))+#endif++#if MIN_VERSION_base(4,9,0)+import Data.Functor.Classes (Eq1(..), Ord1(..), Show1(..), Read1(..))+import qualified Data.Functor.Classes as FC+#endif++#ifdef __GLASGOW_HASKELL__+import qualified Text.Read as R+import GHC.Exts (build)+#endif+#if __GLASGOW_HASKELL__ >= 708+import qualified GHC.Exts (IsList(..))+#endif+#if MIN_VERSION_base(4,7,0)+-- [GHC 7.8.1]: "Data.Coerce" added to base.+import Data.Coerce (coerce)+#endif++------------------------------------------------------------+------------------------------------------------------------+-- $bug25+-- #bug25#+-- Many (but not all) functions which need to reconstruct bytestrings+-- suffer from an asymptotic slowdown; see:+-- <https://github.com/wrengr/bytestring-trie/issues/25 Bug #25>.+-- For clarity, all functions affected by this bug will have a link+-- to this section. This is not a new bug, it has affected all+-- prior versions of this library as well. However, compared to+-- older versions, /bytestring-trie-0.2.7/ mitigates the severity+-- of the bug, and in certain cases to avoids it entirely.+--+-- In particular, this affects the \"keyed\" variants of functions+-- (for folding, traversing, filtering, etc), and anything built+-- from them, including 'toListBy' and various instances which use+-- it.+--+-- Conversely, functions which are unaffected include: those like+-- 'alterBy' which merely pass the query back to the user as a+-- convenience; those which only need to reconstruct a single+-- bytestring (e.g., the priority-queue functions); and+-- 'Data.Trie.matches'\/'matches_'.+++------------------------------------------------------------+-- | Infix variant of 'uncurry'. Currently only used in 'alterBy_'.+-- The fixity-level is like @(<$>)@; but I'm making it nonassociative+-- to avoid any possible\/potential confusion.+infix 4 $$+($$) :: (a -> b -> c) -> (a, b) -> c+($$) = uncurry+{-# INLINE ($$) #-}++{-----------------------------------------------------------+-- ByteString Big-endian Patricia Trie datatype+-----------------------------------------------------------}+{-+In our idealized representation, we use a (directed) discrete graph+to represent our finite state machine. To organize the set of+outgoing arcs from a given Node we have ArcSet be a big-endian+patricia tree like Data.IntMap. In order to simplify things we then+go through a series of derivations.++ data Node a = Accept a (ArcSet a)+ | Reject (Branch a)+ data Arc a = Arc ByteString (Node a)+ data ArcSet a = None+ | One KeyElem (Arc a)+ | Many (Branch a)+ data Branch a = Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+ data Trie a = Empty+ | Start (Arc a) -- [1]++[1]: N.B., we must allow constructing @Start(Arc pre (Reject b))@+for non-null @pre@, so that we can have a shared prefix even though+that prefix itself doesn't have an associated value.++** Squash Arc into One and Start:+For One, this allows combining the initial KeyElem with the rest+of the ByteString, which is purely beneficial. However, it does+introduce some invariants since now we must distinguish NonEmptyBS+vs NullableBS.++ newtype NonEmptyBS = NonEmptyBS ByteString -- Invariant: never null.+ newtype NullableBS = NullableBS Bytestring -- May be null.++ data Node a = Accept a (ArcSet a)+ | Reject (Branch a)+ data ArcSet a = None+ | Arc NonEmptyBS (Node a)+ | Many (Branch a)+ data Branch a = Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+ data Trie a = Empty+ | Start NullableBS (Node a)++** Squash Accept and Reject together:+Maybe[2] beneficial. However, it complicates stating the invariants+about Node's recursion.++ data Node a = Node (Maybe a) (ArcSet a)+ -- Invariant: if Nothing then must be Branch+ data ArcSet a = None+ | Arc NonEmptyBS (Node a)+ | Many (Branch a)+ data Branch a = Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+ data Trie a = Empty+ | Start NullableBS (Node a)++[2]: The above change is certainly beneficial from the perspective+of source-code size/repetition for 'mergeBy', 'intersectBy', and+other operations that operate on two tries in tandem; though it's+unclear whether/how much that transfers to compiled-code size/bloat.+Also since the 'Maybe' isn't unpacked, this introduces an additional+indirection to reach values. Starting at version 0.2.7 there's an+ongoing effort to try to determine whether this change is beneficial+or not, and to quantify how much it affects things.++** Squash Branch into Many:+Purely beneficial, since there's no point in keeping them distinct anymore.++ data Node a = Node (Maybe a) (ArcSet a)+ -- Invariant: if Nothing then must be Branch+ data ArcSet a = None+ | Arc NonEmptyBS (Node a)+ | Branch {Prefix} {Mask} (ArcSet a) (ArcSet a)+ data Trie a = Empty+ | Start NullableBS (Node a)++** Squash Empty/None and Arc/Start together:+Alas, this complicates the invariants about non-null strings.++ data Node a = Node (Maybe a) (ArcSet a)+ -- Invariant: if Nothing then must be Branch+ data Trie a = Empty+ | Arc ByteString (Node a)+ -- Invariant: null string only allowed if both+ -- (a) the Arc is at the root, and+ -- (b) the Node has a value.+ | Branch {Prefix} {Mask} (Trie a) (Trie a)++** Squash Node into Arc:+By this point, purely beneficial. However, the two unseen invariants remain.+-}+++-- [Note: Order of constructors]+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+-- As discussed in "Data.IntMap.Internal", the order of constructors+-- in the definition is the order in which case analysis will always+-- test them. On GHC 9.2.1 I haven't noticed a significant difference+-- from changing the order, but that could be due to bad benchmarking+-- (i.e., reusing the test suite); so I'll trust that this hasn't+-- changed so much since GHC 7.0. The only question is whether+-- @Arc@ or @Branch@ should come first, which depends a lot on the+-- dataset being used.+--+-- This reordering change was performed for version 0.2.7+-- <https://github.com/wrengr/bytestring-trie/commit/75f3d32f7de7457dc7d029b60be3cce8b99c5e80>+++-- | A map from 'ByteString's to @a@. For all the generic functions,+-- note that tries are strict in the @Maybe@ but not in @a@.+--+-- The 'Monad' instance is strange. If a key @k1@ is a prefix of+-- other keys, then results from binding the value at @k1@ will+-- override values from longer keys when they collide. If this is+-- useful for anything, or if there's a more sensible instance, I'd+-- be curious to know.++data Trie a+ = Branch {-# UNPACK #-} !Prefix+ {-# UNPACK #-} !Mask+ !(Trie a) -- Must not be @Empty@.+ !(Trie a) -- Must not be @Empty@.+ | Arc {-# UNPACK #-} !ByteString -- Has nontrivial invariants.+ !(Maybe a)+ !(Trie a) -- Has complex invariants.+ | Empty+ deriving Eq+ -- Prefix/Mask should be deterministic regardless of insertion order+ -- TODO: prove this is so.++-- [Note:Closure] aka: Local 'go' functions and capturing+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+-- Copied from 'IntMap': Care must be taken when using 'go' function+-- which captures an argument. Sometimes (for example when the+-- argument is passed to a data constructor, as in insert), GHC+-- heap-allocates more than necessary. Therefore C-- code must be+-- checked for increased allocation when creating and modifying+-- such functions.++{-----------------------------------------------------------+-- Smart constructors+-----------------------------------------------------------}++{-+-- | A common precondition for ensuring the safety of the following+-- smart constructors.+ifJustThenNoEpsilon :: Maybe a -> Trie a -> Bool+ifJustThenNoEpsilon (Just _) (Arc k (Just _) _) = not (S.null k)+ifJustThenNoEpsilon _ _ = True+-}++-- FIXME: [bug26] <https://github.com/wrengr/bytestring-trie/issues/26>+-- We need to adjust 'arc', 'prepend', 'arcNN', and 'prependNN' to behave+-- more like a zipper, to avoid asymptotic slowdown in corner cases.++-- | Smart constructor to prune @Arc@s that lead nowhere.+--+-- __Preconditions__+-- * @arc _ mv t | ifJustThenNoEpsilon mv t@+arc :: ByteString -> Maybe a -> Trie a -> Trie a+{-# INLINE arc #-}+arc !k mv@(Just _) = Arc k mv+arc k Nothing = prepend k++-- | Variant of 'arc' where the string is known to be non-null.+--+-- __Preconditions__+-- * @arcNN k _ _ | not (S.null k)@+-- * @arcNN _ mv t | ifJustThenNoEpsilon mv t@+arcNN :: ByteString -> Maybe a -> Trie a -> Trie a+{-# INLINE arcNN #-}+arcNN !k mv@(Just _) = Arc k mv+arcNN k Nothing = prependNN k++-- | Prepend a possibly-null string to a trie.+--+-- This function is only very rarely needed; most of the time you+-- already know that the string is non-null, and thus you can call+-- 'prependNN' directly.+--+-- TODO: may actually consider exporting this one, since it could+-- be generally useful and it has no preconditions. Of course,+-- it's susceptible to [bug25][bug26] if used incorrectly...+prepend :: ByteString -> Trie a -> Trie a+{-# INLINE prepend #-}+prepend k+ | S.null k = id+ | otherwise = prependNN k++-- | Prepend a non-null string to a trie.+--+-- __Preconditions__+-- * @prependNN k _ | not (S.null k)@+prependNN :: ByteString -> Trie a -> Trie a+{-# INLINE prependNN #-}+prependNN !_ t@Empty = t+prependNN q t@(Branch{}) = Arc q Nothing t+prependNN q (Arc k mv s) = Arc (S.append q k) mv s+ -- TODO: see [bug26]; should ensure that callers do not nest+ -- calls to this function which all take this @Arc@ case.++-- | > mayEpsilon mv ≡ arc S.empty mv+--+-- __Preconditions__+-- * @mayEpsilon mv t | ifJustThenNoEpsilon mv t@+mayEpsilon :: Maybe a -> Trie a -> Trie a+{-# INLINE mayEpsilon #-}+mayEpsilon (Just v) = epsilon v+mayEpsilon Nothing = id++-- | Canonical name for the empty arc at the top of the trie.+--+-- > epsilon ≡ Arc S.empty . Just+--+-- __Preconditions__+-- * The trie argument must not already have an epsilon value.+epsilon :: a -> Trie a -> Trie a+{-# INLINE epsilon #-}+epsilon = Arc S.empty . Just+++-- | Smart @Branch@ constructor: prunes @Empty@ on both sides.+-- This function does no other work besides pruning, so the @Prefix@,+-- @Mask@, and ordering of the 'Trie's must all be as if calling+-- the @Branch@ constructor directly.+branch :: Prefix -> Mask -> Trie a -> Trie a -> Trie a+{-# INLINE branch #-}+branch !_ !_ Empty r = r+branch _ _ l Empty = l+branch p m l r = Branch p m l r++-- | Smart @Branch@ constructor: prunes @Empty@ on left side only.+--+-- __Preconditions__+-- * the right trie is not @Empty@.+branchL :: Prefix -> Mask -> Trie a -> Trie a -> Trie a+{-# INLINE branchL #-}+branchL !_ !_ Empty r = r+branchL p m l r = Branch p m l r++-- | Smart @Branch@ constructor: prunes @Empty@ on right side only.+--+-- __Preconditions__+-- * the left trie is not @Empty@.+branchR :: Prefix -> Mask -> Trie a -> Trie a -> Trie a+{-# INLINE branchR #-}+branchR !_ !_ l Empty = l+branchR p m l r = Branch p m l r++-- | Smart constructor to join two tries into a @Branch@ with maximal+-- prefix sharing, and in the correct left\/right order. Requires+-- knowing the prefixes, but can combine either @Branch@es or @Arc@s.+--+-- __Preconditions__+-- * Both tries must be non-@Empty@.+-- * The two prefixes /must not/ be able to match entirely!+graft :: Prefix -> Trie a -> Prefix -> Trie a -> Trie a+{-# INLINE graft #-}+graft p0 t0 p1 t1+ | zero p0 m = Branch p m t0 t1+ | otherwise = Branch p m t1 t0+ where+ m = getMask p0 p1+ p = applyMask p0 m++-- | Shorthand for prepending a non-null string to a 'graft'. This+-- is mainly useful for when @(p,k0,k1)@ came from 'breakMaximalPrefix'.+--+-- __Preconditions__+-- * The prefix must be non-null.+-- * Each trie must agree with their key (hence must also be non-@Empty@).+-- * The keys must not have matching prefixes.+wye :: ByteString+ -> ByteString -> Trie a+ -> ByteString -> Trie a+ -> Trie a+wye p k0 t0 k1 t1 =+ Arc p Nothing $ graft (arcPrefix k0) t0 (arcPrefix k1) t1++-- TODO: this is only used by 'mergeBy' (since 'intersectBy' returns+-- @Empty@ in lieu of @Branch@ for the latter cases); so maybe we+-- should move this to be a local definition there?+--+-- | Smart constructor to join two @Arc@s into a @Branch@ when possible,+-- and to 'breakMaximalPrefix' otherwise.+--+-- __Preconditions__+-- * Both tries must be non-@Empty@.+arcMerge+ :: ByteString -> Trie a+ -> ByteString -> Trie a+ -> (ByteString -> ByteString -> ByteString -> Trie a)+ -> Trie a+{-# INLINE arcMerge #-}+arcMerge k0 t0 k1 t1 whenMatch+ | m == 0 =+ case breakMaximalPrefix k0 k1 of+ (pre, k0', k1')+ -- TODO: change this into an 'assert' instead.+ | S.null pre -> impossible "arcMerge" -- perfect 'arcPrefix' match, yet no 'breakMaximalPrefix' prefix.+ | otherwise -> whenMatch pre k0' k1'+ | zero p0 m = Branch p m t0 t1+ | otherwise = Branch p m t1 t0+ where+ p0 = arcPrefix k0+ p1 = arcPrefix k1+ m = getMask p0 p1+ p = applyMask p0 m++-- It would be better if arcs used+-- 'Data.ByteString.TrieInternal.wordHead' somehow, that way+-- we can see 4/8/?*Word8 at a time instead of just one.+-- But that makes maintaining invariants ...difficult :(++-- | Get the equivalent of the @Prefix@ stored in a @Branch@, but+-- for an @Arc@.+arcPrefix :: ByteString -> Prefix+{-# INLINE arcPrefix #-}+arcPrefix k+ | S.null k = 0 -- for lack of a better value+ | otherwise = SU.unsafeHead k+++{-----------------------------------------------------------+-- Error messages+-----------------------------------------------------------}++-- TODO: move off to "Data.Trie.Errors"?+-- TODO: shouldn't we inline the logic and just NOINLINE the string+-- constant? There are only three use sites, which themselves aren't+-- inlined...+-- TODO: this is almost identical to 'arcPrefix'; the only difference+-- is that we use this one for matching a query against a trie,+-- whereas we use 'arcPrefix' when matching two tries together.+-- That said, since our test suite never throws this error, it+-- should be safe to use 'arcPrefix' everywhere instead. Or, if+-- we want to preserve the semantic distinction, then we could start+-- using 'Control.Exception.assert' to hoist the null-check out to+-- where it belongs and still allow it to compile away. Conversely,+-- note that 'arcPrefix' is never called with a null string either+-- (since null strings are only ever allowed for epsilon values;+-- and all the use-sites of 'arcPrefix' are after handling those+-- epsilons, or otherwise guarded).+errorLogHead :: String -> ByteString -> ByteStringElem+{-# NOINLINE errorLogHead #-}+errorLogHead fn q+ | S.null q = error $ "Data.Trie.Internal." ++ fn ++": found null subquery"+ | otherwise = SU.unsafeHead q++------------------------------------------------------------+------------------------------------------------------------+++{-----------------------------------------------------------+-- Instances: Eq, Eq1+-----------------------------------------------------------}++{-+-- (2021.12.31): remove the definition of @(/=)@ for:+-- <https://github.com/haskell/core-libraries-committee/issues/3>+--+-- 'IntMap' defines their own instance so as to check the Mask+-- before the Prefix; and they have done so since at least version+-- 0.5.0.0 (2011). So I assume the performance benefits of doing+-- that are good enough to be worth it; thus, we'll do the same.+--+-- TODO: benchmark!!+instance Eq a => Eq (Trie a) where+ (==) = equal++-- TODO: mark this INLINABLE to specialize on the Eq instance? Why doesn't IntMap?+-- TODO: Alternatively, why doesn't IntMap simply reuse the 'liftEq' implementation?+equal :: Eq a => Trie a -> Trie a -> Bool+equal (Branch p0 m0 l0 r0)+ (Branch p1 m1 l1 r1) = m0 == m1 && p0 == p1 && equal l0 l1 && equal r0 r1+equal (Arc k0 mv0 t0)+ (Arc k1 mv1 t1) = k0 == k1 && mv0 == mv1 && equal t0 t1+equal Empty Empty = True+equal _ _ = False+-}++#if MIN_VERSION_base(4,9,0)+-- | @since 0.2.7+instance Eq1 Trie where+ liftEq = equal1++-- TODO: why doesn't IntMap close over @eq@? Does it really cost so much more?+-- TODO: INLINEABLE?+equal1 :: (a -> b -> Bool) -> Trie a -> Trie b -> Bool+equal1 eq (Branch p0 m0 l0 r0) (Branch p1 m1 l1 r1) =+ m0 == m1 && p0 == p1 && equal1 eq l0 l1 && equal1 eq r0 r1+equal1 eq (Arc k0 mv0 t0) (Arc k1 mv1 t1) =+ k0 == k1 && liftEq eq mv0 mv1 && equal1 eq t0 t1+equal1 _ Empty Empty = True+equal1 _ _ _ = False+#endif++{-----------------------------------------------------------+-- Instances: Ord, Ord1+-----------------------------------------------------------}++-- |+-- __Warning__: This instance suffers unnecessarily from+-- <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+instance Ord a => Ord (Trie a) where+ compare t0 t1 = compare (toList t0) (toList t1)++#if MIN_VERSION_base(4,9,0)+-- |+-- __Warning__: This instance suffers unnecessarily from+-- <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+instance Ord1 Trie where+ liftCompare cmp t0 t1 =+ liftCompare (liftCompare cmp) (toList t0) (toList t1)+#endif++{-----------------------------------------------------------+-- Instances: Show, Show1+-----------------------------------------------------------}++-- This instance does not unveil the innards of our abstract type.+-- It doesn't emit truly proper Haskell code though, since ByteStrings+-- are printed as (ASCII) Strings, but that's not our fault.+--+-- |+-- __Warning__: This instance suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.2+instance (Show a) => Show (Trie a) where+ showsPrec p t = showParen (p > 10)+ $ ("fromList " ++) . shows (toList t)++#if MIN_VERSION_base(4,9,0)+-- |+-- __Warning__: This instance suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+instance Show1 Trie where+ liftShowsPrec sp sl p t =+ FC.showsUnaryWith (liftShowsPrec sp' sl') "fromList" p (toList t)+ where+ sp' = liftShowsPrec sp sl+ sl' = liftShowList sp sl+#endif++-- | Visualization fuction for debugging.+showTrie :: (Show a) => Trie a -> String+showTrie t = shows' id t ""+ where+ spaces f = map (const ' ') (f "")++ shows' _ Empty = (".\n"++)+ shows' ss (Branch p m l r) =+ let s' = ("--"++) . shows p . (","++) . shows m . ("-+"++)+ -- NOTE: GHC-9.10 warns about 'tail' being a partial function.+ ss' = ss . (drop 1 (spaces s') ++)+ in s' . shows' (ss' . ("|"++)) l+ . ss' . ("|\n"++)+ . ss' . ("`"++) . shows' (ss' . (" "++)) r+ shows' ss (Arc k mv t') =+ let s' = ("--"++) . shows k+ . maybe id (\v -> ("-("++) . shows v . (")"++)) mv+ . ("--"++)+ in s' . shows' (ss . (spaces s' ++)) t'++{-----------------------------------------------------------+-- Instances: Read, Read1+-----------------------------------------------------------}++-- | @since 0.2.7+instance (Read a) => Read (Trie a) where+#ifdef __GLASGOW_HASKELL__+ readPrec = R.parens . R.prec 10 $ do+ R.Ident "fromList" <- R.lexP+ fromList <$> R.readPrec++ readListPrec = R.readListPrecDefault+#else+ readsPrec p = readParen (p > 10) $ \ r0 -> do+ ("fromList", r1) <- lex r0+ (xs, r2) <- reads r1+ return (fromList xs, r2)+#endif++#if MIN_VERSION_base(4,9,0)+-- | @since 0.2.7+instance Read1 Trie where+ liftReadsPrec rp rl =+ FC.readsData $ FC.readsUnaryWith (liftReadsPrec rp' rl')+ "fromList" fromList+ where+ rp' = liftReadsPrec rp rl+ rl' = liftReadList rp rl+#endif++{-----------------------------------------------------------+-- Instances: Binary+-----------------------------------------------------------}++{-+-- TODO: consider an instance more like the new one for Data.Map+-- (also for Data.IntMap), that is:+instance (Binary a) => Binary (Set.Set a) where+ put s = put (size s) <> mapM_ put (toAscList s)+ get = liftM fromDistinctAscList get+-- It would require redoing all the work to split bytestrings, and+-- have the overhead from storing duplicated prefixes, but is forward+-- compatible to whatever representation changes, and doesn't have+-- the invariants problem.+-- BUG: However, that would suffer from+-- <https://github.com/wrengr/bytestring-trie/issues/25>, because+-- 'toList'\/@toAscList@ does.+-}+instance (Binary a) => Binary (Trie a) where+ put Empty = do put (0 :: Word8)+ put (Arc k mv t) = do put (1 :: Word8); put k; put mv; put t+ put (Branch p m l r) = do put (2 :: Word8); put p; put m; put l; put r++ -- BUG(github#21): need to verify the invariants!+ get = do tag <- get :: Get Word8+ case tag of+ 0 -> return Empty+ 1 -> liftM3 Arc get get get+ _ -> liftM4 Branch get get get get++-- TODO: consider adding _cereal_:'Serialize' instance. (Though that adds dependencies on: array, bytestring-builder, fail, ghc-prim). THe instances for Map/IntMap are similar to the commented ones above, though the getter uses 'fromList' rather than assuming distinct or ascending.++-- TODO: potentially consider <https://github.com/mgsloan/store#readme> as well, though probably not since that has a ton of dependencies.++-- TODO: also consider 'Storable', though that seems less appropriate for data structures than for structs and scalars...++{-----------------------------------------------------------+-- Instances: NFData+-----------------------------------------------------------}++-- | @since 0.2.7+instance NFData a => NFData (Trie a) where+ rnf Empty = ()+ rnf (Arc _ mv t) = rnf mv `seq` rnf t+ rnf (Branch _ _ l r) = rnf l `seq` rnf r++{-+-- TODO: do we want/need these?+#ifdef __GLASGOW_HASKELL__+instance Data.Data.Data (Trie a) where ...+-- See 'IntMap' for how to do this without sacrificing abstraction.+#endif++-- I think this macro is defined in "containers.h"?+INSTANCE_TYPEABLE(Trie)++-- What about deriving Generic?+-}++{-----------------------------------------------------------+-- Instances: Functor+-----------------------------------------------------------}++instance Functor Trie where+ {-# INLINE fmap #-}+ fmap = fmapTrie+#ifdef __GLASGOW_HASKELL__+ -- Non-default definition since 0.2.7+ -- Leaving this definition inline and avoiding closure over @v@+ -- because that's what 'IntMap' does.+ _ <$ Empty = Empty+ v <$ (Arc k Nothing t) = Arc k Nothing (v <$ t)+ v <$ (Arc k (Just _) t) = Arc k (Just v) (v <$ t)+ v <$ (Branch p m l r) = Branch p m (v <$ l) (v <$ r)+#endif++-- | Implementation of 'fmap', floated out so that we can give it rewrite rules.+fmapTrie :: (a -> b) -> Trie a -> Trie b+fmapTrie f = go+ where+ go Empty = Empty+ go (Arc k Nothing t) = Arc k Nothing (go t)+ go (Arc k (Just v) t) = Arc k (Just (f v)) (go t)+ go (Branch p m l r) = Branch p m (go l) (go r)+#ifdef __GLASGOW_HASKELL__+{-# NOINLINE [1] fmapTrie #-}+{-# RULES+"fmapTrie/fmapTrie" forall f g xs .+ fmapTrie f (fmapTrie g xs) = fmapTrie (f . g) xs+ #-}+-- 'IntMap' doesn't bother giving a rule for 'id'; so I guess we won't either.+#if MIN_VERSION_base(4,7,0)+{-# RULES "fmapTrie/coerce" fmapTrie coerce = coerce #-}+#endif+#endif++-- TODO: strict version of fmap. Is there a canonical name\/class for this yet?++{-----------------------------------------------------------+-- Instances: Traversable, Applicative, Monad+-----------------------------------------------------------}++instance Traversable Trie where+ -- TODO: 'IntMap' marks this INLINE and floats out an INLINE+ -- top-level definition; no rules though, afaict.+ traverse f = go+ where+ go Empty = pure Empty+ go (Arc k Nothing t) = fmap (Arc k Nothing) (go t)+ go (Arc k (Just v) t) = liftA2 (Arc k . Just) (f v) (go t)+ go (Branch p m l r) = liftA2 (Branch p m) (go l) (go r)++-- | Keyed version of 'traverse'.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+traverseWithKey+ :: Applicative f => (ByteString -> a -> f b) -> Trie a -> f (Trie b)+{-# INLINE traverseWithKey #-}+traverseWithKey f = go Nil+ where+ -- See [Note:LazyRLBS].+ go _ Empty = pure Empty+ go q (Branch p m l r) = liftA2 (Branch p m) (go q l) (go q r)+ go q (Arc k Nothing t) = fmap (Arc k Nothing) (go (q +>! k) t)+ go q (Arc k (Just v) t) =+ let q' = toStrict (q +>? k)+ in liftA2 (Arc k . Just) (f q' v) (go (fromStrict q') t)++-- [Note:LazyRLBS]: We avoid making the RLBS parameter strict, to+-- avoid incuring the cost of 'toStrict' if the user's function+-- does not force it. However, if they do force it, then we'll+-- still have the <https://github.com/wrengr/bytestring-trie/issues/25>+-- problem. Using RLBS only reduces the constant factor of the+-- quadratic.++------------------------------------------------------------+-- TODO: would make more sense to use intersection\/zip semantics here,+-- rather than the overlaid-unionL semantics of the 'Monad'. Alas,+-- done is done. In a future major version we can try changing+-- that, and introducing newtype wrappers for this overlaid\/unionL+-- version (and the prospective underlaid\/unionR version).+-- TODO: see also <https://hackage.haskell.org/package/semialign>+--+-- | @since 0.2.2+instance Applicative Trie where+ pure = singleton S.empty+ t0 <*> t1 = t0 >>= (<$> t1)+ -- TODO: can we do better than these defaults?+ -- t0 *> t1 = (id <$ t0) <*> t1+ -- t0 <* t1 = (const <$> t0) <*> t1 -- actually uses @liftA2 const t0 t1@+#if MIN_VERSION_base(4,10,0)+ -- liftA2 f t0 t1 = (f <$> t0) <*> t1+#endif+ {-+ -- Inlining and case-of-case yields the following (which GHC+ -- could surely derive on its own):+ Empty *> _ = Empty+ Branch p m l r *> t1 = branch p m (l *> t1) (r *> t1)+ Arc k Nothing s *> t1 = prependNN k (s *> t1)+ Arc k (Just _) s *> t1 = prepend k (t1 `unionL` (s *> t1))++ -- This one is marginally better, since we can use @(<$)@ in the Accept case.+ Empty <* _ = Empty+ Branch p m l r <* t1 = branch p m (l <* t1) (r <* t1)+ Arc k Nothing s <* t1 = prependNN k (s <* t1)+ Arc k (Just v) s <* t1 = prepend k ((v <$ t1) `unionL` (s <* t1))++ -- This one took a lot of inlining\/massaging, so might be worth it...+ -- It's easier to see the structure if we define a closure+ -- @(liftA2 f _ t1)@, but unclear if that would hurt the improvement+ -- of the implementation.+ liftA2 f Empty _ = Empty+ liftA2 f (Branch p m l r) t1 = branch p m (liftA2 f l t1) (liftA2 f r t1)+ liftA2 f (Arc k Nothing s) t1 = prependNN k (liftA2 f s t1)+ liftA2 f (Arc k (Just v) s) t1 = prepend k ((f v <$> t1) `unionL` liftA2 f s t1)+ -}++------------------------------------------------------------+-- Does this even make sense? It's not nondeterminism like lists+-- and sets. If no keys were prefixes of other keys it'd make sense+-- as a decision-tree; but since keys /can/ prefix, tries formed+-- from shorter keys can shadow the results from longer keys due+-- to the 'unionL'. It does seem to follow the laws though... What+-- computation could this possibly represent?+--+-- 1. return x >>= f ≡ f x+-- 2. m >>= return ≡ m+-- 3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g)+--+-- | @since 0.2.2+instance Monad Trie where+-- Since base-4.8 (ghc-7.10.1) we have the default @return = pure@.+-- Since ghc-9.2.1 we get a warning about providing any other+-- definition, and should instead define both 'pure' and @(*>)@+-- directly, leaving 'return' and @(>>)@ as their defaults so they+-- can eventually be removed from the class.+-- <https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/monad-of-no-return>+#if (!(MIN_VERSION_base(4,8,0)))+ return = pure+#endif+ -- FIXME: See [bug26].+ (>>=) Empty _ = empty+ (>>=) (Branch p m l r) f = branch p m (l >>= f) (r >>= f)+ (>>=) (Arc k Nothing t) f = prependNN k (t >>= f)+ (>>=) (Arc k (Just v) t) f = prepend k (f v `unionL` (t >>= f))+ where unionL = mergeBy (\x _ -> Just x)+++{-----------------------------------------------------------+-- Instances: Semigroup, Monoid+-----------------------------------------------------------}++#if MIN_VERSION_base(4,9,0)+-- The "Data.Semigroup" module is in base since 4.9.0.0; but having+-- the 'Semigroup' superclass for the 'Monoid' instance only comes+-- into force in base 4.11.0.0.+-- | @since 0.2.5+instance (Semigroup a) => Semigroup (Trie a) where+ (<>) = mergeBy $ \x y -> Just (x <> y)+ -- Non-default definition since 0.2.7+ stimes = fmap . stimes+#endif+++-- This instance is more sensible than Data.IntMap and Data.Map's+instance (Monoid a) => Monoid (Trie a) where+ mempty = empty+#if MIN_VERSION_base(4,11,0)+ -- Now that the canonical instance is the default, don't define+ -- 'mappend', in anticipation of Phase 4 of:+ -- <https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/semigroup-monoid>+{-+--if MIN_VERSION_base(4,9,0)+ -- GHC 8.0/8.2 -Wnoncanonical-monoid-instances wants this+ -- definition, even though 'Semigroup' isn't a superclass of+ -- 'Monoid' until base-4.11 so it would require additional+ -- constraints on the instance. Since we're only supporting+ -- these older versions for legacy reasons, there's no reason+ -- to bother adhering to the -Wcompat here.+ mappend = (<>)+-}+#else+ mappend = mergeBy $ \x y -> Just (x `mappend` y)+#endif+ -- TODO: Can we improve `mconcat` over the default?+++{-----------------------------------------------------------+-- Instances: Alternative, MonadPlus+-----------------------------------------------------------}++-- Since the Monoid instance isn't natural in @a@, I can't think+-- of any other sensible instance for MonadPlus. It's as specious+-- as Maybe, IO, and STM's instances though.+--+-- MonadPlus laws: <http://www.haskell.org/haskellwiki/MonadPlus>+-- 1. <Trie a, mzero, mplus> forms a monoid+-- 2. mzero >>= f ≡ mzero+-- 3. m >> mzero ≡ mzero+-- 4. mplus m n >>= k ≡ mplus (m >>= k) (n >>= k)+-- 4' mplus (return a) n ≡ return a+{-+-- Follows #1, #1, and #3. But it does something like 4' instead+-- of actually doing #4 (since we'd merge the trees generated by+-- @k@ for conflicting values)+--+-- TODO: cf Control.Applicative.Alternative (base-4, but not Hugs).+-- But (<*>) gets odd when the function is not 'pure'... maybe+-- helpful though.+instance MonadPlus Trie where+ mzero = empty+ mplus = unionL where unionL = mergeBy (\x _ -> Just x)+-}+++{-----------------------------------------------------------+-- Extra mapping and filtering functions+-----------------------------------------------------------}++{-----------------------------------------------------------+-- Pseudo-instances: Filterable, Witherable+-----------------------------------------------------------}++-- We avoid depending on the _filterable_ package because it combines+-- too many things into its @Filterable@ class. And we avoid using+-- the _witherable_ package because it has too many dependencies,+-- and too many orphan instances. However, we go with the names+-- (mostly[1]) and laws as phrased by _witherable_.+--+-- [1]: I'm rather not a fan of @mapMaybe@, despite its pervasiveness.+-- And similarly for @catMaybes@ etc. That's actually one of the+-- reasons I prefer the _witherable_ package over _filterable_:+-- because of the name 'wither' instead of @mapMaybeA@ :)+++-- | Apply a function to all values, potentially removing them.+--+-- ==== __Laws__+-- [/Fission/]+-- @'filterMap' f ≡ 'fmap' ('Data.Maybe.fromJust' . f) . 'filter' ('Data.Maybe.isJust' . f)@+--+-- [/Fusion/]+-- @'fmap' f . 'filter' g ≡ 'filterMap' (\\v -> f v '<$' 'Control.Monad.guard' (g v))@+--+-- [/Conservation/]+-- @'filterMap' ('Just' . f) ≡ 'fmap' f@+--+-- [/Composition/]+-- @'filterMap' f . 'filterMap' g ≡ 'filterMap' (f 'Control.Monad.<=<' g)@+--+-- The fission\/fusion laws are essentially the same, they differ+-- only in which direction is more \"natural\" for use as a rewrite+-- rule. The conservation law is just a special case of fusion,+-- but it's a particularly helpful one to take note of.+--+filterMap :: (a -> Maybe b) -> Trie a -> Trie b+filterMap f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = arc k (f v) (go t)+ start t = go t+ -- FIXME: See [bug26].+ go Empty = empty+ go (Arc k Nothing t) = prependNN k (go t)+ go (Arc k (Just v) t) = arcNN k (f v) (go t)+ go (Branch p m l r) = branch p m (go l) (go r)+-- TODO: rewrite rule for the latter three laws. (The fission law+-- is unlikely to be very helpful.)+-- TODO: why not implement as @contextualFilterMap (const . f)@ ?+-- Does that actually incur additional overhead?+++-- Some translations:+-- @'filter' f ≡ 'filterMap' (\\v -> if f v then 'Just' v else 'Nothing')@+-- @'filter' f ≡ 'filterMap' (('<$') '<*>' 'Control.Monad.guard' . f)@+-- @'filter' f ≡ 'filterMap' (\\v -> v '<$' 'Control.Monad.guard' (f v))@+--+-- | Retain only those values which satisfy some predicate.+--+-- ==== __Laws__+-- [/Definition/]+-- @'filter' f ≡ 'filterMap' (\\v -> v '<$' 'Control.Monad.guard' (f v))@+--+-- [/Composition/]+-- @'filter' f . 'filter' g ≡ 'filter' ('liftA2' ('&&') f g)@+--+-- The definition above is a special case of the fusion law for+-- 'filterMap'. (Also, the name just means definitional-equality;+-- it's not the actual implementation used.)+--+-- @since 0.2.7+filter :: (a -> Bool) -> Trie a -> Trie a+filter f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = arcB k v (f v) (go t)+ start t = go t+ -- FIXME: See [bug26].+ go Empty = empty+ go (Arc k Nothing t) = prependNN k (go t)+ go (Arc k (Just v) t) = arcNNB k v (f v) (go t)+ go (Branch p m l r) = branch p m (go l) (go r)++-- | > arcB k v b ≡ arc k (v <$ guard b)+arcB :: ByteString -> a -> Bool -> Trie a -> Trie a+{-# INLINE arcB #-}+arcB k v True = Arc k (Just v)+arcB k _ False = prepend k++-- | > arcNNB k v b ≡ arcNN k (v <$ guard b)+arcNNB :: ByteString -> a -> Bool -> Trie a -> Trie a+{-# INLINE arcNNB #-}+arcNNB k v True = Arc k (Just v)+arcNNB k _ False = prependNN k+++{-+#if MIN_VERSION_base(4,9,0)+import Data.Functor.Compose (Compose(Compose))+#endif+#if MIN_VERSION_base(4,8,0)+import Data.Functor.Identity (Identity(Identity))+#endif+-}++-- BUG: Is there any other way to get the Haddock to force linebreaks,+-- other than using birdtracks which have a whole different+-- stylization?+-- TODO: is commutative monad sufficient, or are there other+-- requirements too? If it does in fact hold for commutative monads,+-- then we should state it as a law (like we do for naturality).+--+-- | An effectful version of 'filterMap'.+--+-- ==== __Laws__+-- [/Naturality/]+-- @'wither' (t . f) ≡ t . 'wither' f@,+-- for any /applicative-transformation/ @t@+--+-- [/Purity/]+-- @'wither' ('pure' . f) ≡ 'pure' . 'filterMap' f@+--+-- [/Conservation/]+-- @'wither' ('fmap' 'Just' . f) ≡ 'traverse' f@+--+-- [/Horizontal Composition/]+-- @'wither' f \`under\` 'wither' g ≡ 'wither' (wither_Maybe f \`under\` g)@,+-- where:+--+-- > under :: Functor f+-- > => (b -> g c)+-- > -> (a -> f b)+-- > -> a -> Compose f g c+-- > under g f = Compose . fmap g . f+-- >+-- > -- | Variant of wither for Maybe instead of Trie.+-- > wither_Maybe :: Applicative f+-- > => (a -> f (Maybe b))+-- > -> Maybe a -> f (Maybe b)+-- > wither_Maybe f = fmap join . traverse f+--+-- Note that the horizontal composition law is using two different+-- applicative functors. Conversely, a vertical composition law+-- would have the form: @'wither' f 'Control.Monad.<=<' 'wither' g ≡ ...@;+-- however, we cannot have such a law except when the applicative+-- functor is in fact a commutative monad (i.e., the order of effects+-- doesn't matter). For the curious, the terminology of+-- <https://ncatlab.org/nlab/show/horizontal+composition \"horizontal\" composition> vs+-- <https://ncatlab.org/nlab/show/vertical+composition \"vertical\" composition>+-- comes from category theory.+--+-- Although the horizontal composition law may look baroque, it is+-- helpful to compare it to the composition law for 'traverse'+-- itself:+--+-- @'traverse' f \`under\` 'traverse' g ≡ 'traverse' (f \`under\` g)@+--+-- @since 0.2.7+wither :: Applicative f => (a -> f (Maybe b)) -> Trie a -> f (Trie b)+wither f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = liftA2 (arc k) (f v) (go t)+ start t = go t+ -- FIXME: See [bug26].+ go Empty = pure empty+ go (Arc k Nothing t) = fmap (prependNN k) (go t)+ go (Arc k (Just v) t) = liftA2 (arcNN k) (f v) (go t)+ go (Branch p m l r) = liftA2 (branch p m) (go l) (go r)++-- Some other spellings of the translation:+-- @'filterA' f ≡ 'wither' (\\v -> (\\b -> if b then 'Just' v else 'Nothing') '<$>' f v)@+-- @'filterA' f ≡ 'wither' (\\v -> (\\b -> v '<$' 'Control.Monad.guard' b) '<$>' f v)@+-- @'filterA' f ≡ 'wither' ('fmap' . (. 'Control.Monad.guard') . ('<$') '<*>' f)@+--+-- An alternative variant of 'underA2' which has a nicer type.+-- Alas, that just makes the twist show up in the law itself; also+-- the name is peculiar since it's the second argument that's run+-- under the first. Decisions decisions.+-- > underF2 :: (Functor f, Functor g)+-- > => (b -> c -> d)+-- > -> (a -> f b)+-- > -> (a -> g c)+-- > -> a -> Compose f g d+-- > underF2 h f g a = Compose (f a <&> ((g a <&>) . h))+--+--+-- | An effectful version of 'filter'.+--+-- ==== __Laws__+-- [/Definition/]+-- @'filterA' f ≡ 'wither' (\\v -> (v '<$') . 'Control.Monad.guard' '<$>' f v)@+--+-- [/Naturality/]+-- @'filterA' (t . f) ≡ t . 'filterA' f@,+-- for any /applicative-transformation/ @t@+--+-- [/Purity/]+-- @'filterA' ('pure' . f) ≡ 'pure' . 'filter' f@+--+-- [/Horizontal Composition/]+-- @'filterA' f \`under\` 'filterA' g ≡ 'filterA' (underA2 ('&&') f g)@,+-- where+--+-- > -- Like 'liftA2' for the @(a->)@ monad, but horizontal.+-- > -- The function definition should (hopefully) be straightforward+-- > -- to follow; however, do beware the oddly criss-crossed types+-- > -- for @g@ and @f@.+-- > underA2 :: (Applicative f, Applicative g)+-- > => (b -> c -> d)+-- > -> (a -> g b)+-- > -> (a -> f c)+-- > -> a -> Compose f g d+-- > underA2 h g f = liftA2 (liftA2 h) (g `under` pure) (pure `under` f)+--+-- For the definition of @under@ and more details about horizontal+-- composition, see the laws section of 'wither'.+--+-- @since 0.2.7+filterA :: Applicative f => (a -> f Bool) -> Trie a -> f (Trie a)+filterA f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = liftA2 (arcB k v) (f v) (go t)+ start t = go t+ -- FIXME: See [bug26].+ go Empty = pure empty+ go (Arc k Nothing t) = fmap (prependNN k) (go t)+ go (Arc k (Just v) t) = liftA2 (arcNNB k v) (f v) (go t)+ go (Branch p m l r) = liftA2 (branch p m) (go l) (go r)+++-- | Keyed version of 'filterMap'.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+mapBy :: (ByteString -> a -> Maybe b) -> Trie a -> Trie b+-- TODO: why not implement as @contextualMapBy (\k v _ -> f k v)@ ?+-- Does that actually incur additional overhead?+mapBy f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = arc k (f k v) (go (fromStrict k) t)+ start t = go Nil t+ -- FIXME: See [bug26].+ -- See [Note:LazyRLBS].+ go _ Empty = empty+ go q (Branch p m l r) = branch p m (go q l) (go q r)+ go q (Arc k Nothing t) = prependNN k (go (q +>! k) t)+ go q (Arc k (Just v) t) = arcNN k (f q' v) (go (fromStrict q') t)+ where q' = toStrict (q +>? k)+++-- | A variant of 'fmap' which provides access to the subtrie rooted+-- at each value.+--+-- @since 0.2.3+contextualMap :: (a -> Trie a -> b) -> Trie a -> Trie b+contextualMap f = go+ where+ go Empty = Empty+ go (Arc k Nothing t) = Arc k Nothing (go t)+ go (Arc k (Just v) t) = Arc k (Just (f v t)) (go t)+ go (Branch p m l r) = Branch p m (go l) (go r)+++-- | A variant of 'contextualMap' which evaluates the function strictly.+--+-- @since 0.2.3+contextualMap' :: (a -> Trie a -> b) -> Trie a -> Trie b+contextualMap' f = go+ where+ go Empty = Empty+ go (Arc k Nothing t) = Arc k Nothing (go t)+ go (Arc k (Just v) t) = Arc k (Just $! f v t) (go t)+ go (Branch p m l r) = Branch p m (go l) (go r)+++-- | Contextual variant of 'filterMap'.+--+-- @since 0.2.3+contextualFilterMap :: (a -> Trie a -> Maybe b) -> Trie a -> Trie b+contextualFilterMap f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = arc k (f v t) (go t)+ start t = go t+ -- FIXME: See [bug26].+ go Empty = empty+ go (Arc k Nothing t) = prependNN k (go t)+ go (Arc k (Just v) t) = arcNN k (f v t) (go t)+ go (Branch p m l r) = branch p m (go l) (go r)+++-- | Contextual variant of 'mapBy', aka keyed variant of 'contextualFilterMap'.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.3+contextualMapBy :: (ByteString -> a -> Trie a -> Maybe b) -> Trie a -> Trie b+contextualMapBy f = start+ where+ -- Handle epsilon values before entering the main recursion.+ start (Arc k (Just v) t) = arc k (f k v t) (go (fromStrict k) t)+ start t = go Nil t+ -- FIXME: See [bug26].+ -- See [Note:LazyRLBS].+ go _ Empty = empty+ go q (Branch p m l r) = branch p m (go q l) (go q r)+ go q (Arc k Nothing t) = prependNN k (go (q +>! k) t)+ go q (Arc k (Just v) t) = arcNN k (f q' v t) (go (fromStrict q') t)+ where q' = toStrict (q +>? k)+++{-----------------------------------------------------------+-- Basic functions+-----------------------------------------------------------}+-- TODO: probably want to hoist this up top++-- | \(\mathcal{O}(1)\). Construct the empty trie.+empty :: Trie a+{-# INLINE empty #-}+empty = Empty+++-- | \(\mathcal{O}(1)\). Is the trie empty?+null :: Trie a -> Bool+{-# INLINE null #-}+null Empty = True+null _ = False+++-- | \(\mathcal{O}(1)\). Construct a singleton trie.+singleton :: ByteString -> a -> Trie a+{-# INLINE singleton #-}+singleton k v = Arc k (Just v) Empty+-- For singletons, don't need to verify invariant on arc length >0+++-- | \(\mathcal{O}(n)\). Get count of elements in trie.+size :: Trie a -> Int+{-# INLINE size #-}+size = size' 0++-- This is just @F.foldl' (\n _ -> n+1) 0@ manually inlined\/specialized.+-- Thus, see our implementation of 'foldl'' to see why we use this+-- particular phrasing out of the very many alternatives.+size' :: Int -> Trie a -> Int+size' !n Empty = n+size' n (Arc _ Nothing t) = size' n t+size' n (Arc _ (Just _) t) = size' (n + 1) t+size' n (Branch _ _ l r) = size' (size' n l) r+++{-----------------------------------------------------------+-- Instances: Foldable+-----------------------------------------------------------}++-- [Note:FoldEta]: For all the folding functions, we take only the+-- two algebra arguments on the left of the \"=\", leaving the+-- 'Trie' argument as a lambda on the right of the \"=\". This is+-- to allow the functions to be inlined when passed only the two+-- algebra arguments, rather than requiring all three arguments+-- before being inlined.++instance Foldable Trie where+ -- TODO: 'IntMap' marks 'fold' as INLINABLE; however, that seems+ -- to introduce a bit of slowdown with respect to 'foldMap'+ -- (which goes away when marked INLINE instead); so we should+ -- do more to discern which inlinement is preferable.+ {-# INLINE fold #-}+ fold = go+ where+ go Empty = mempty+ go (Arc _ Nothing t) = go t+ go (Arc _ (Just v) t) = v `mappend` go t+ go (Branch _ _ l r) = go l `mappend` go r+ {-# INLINE foldMap #-}+ foldMap f = go+ where+ go Empty = mempty+ go (Arc _ Nothing t) = go t+ go (Arc _ (Just v) t) = f v `mappend` go t+ go (Branch _ _ l r) = go l `mappend` go r+#if MIN_VERSION_base(4,13,0)+ -- TODO: float out this definition so folks can still use it+ -- on earlier versions of base?+ -- TODO: verify order of 'mappend' on some non-commutative monoid!+ {-# INLINE foldMap' #-}+ foldMap' f = go mempty+ where+ -- Benchmarking on GHC 9.2.1 indicates that for this function+ -- the (m,t) argument ordering is somewhat (~3%) faster+ -- than the (t,m) order; and both allocate the same.+ -- This differs from the case for 'foldr'' and 'foldl';+ -- though I'm not sure why.+ -- TODO: Once we disable HPC, now it's looking like the+ -- flopped version is faster afterall...+ go !m Empty = m+ go m (Arc _ Nothing t) = go m t+ go m (Arc _ (Just v) t) = go (m `mappend` f v) t+ go m (Branch _ _ l r) = go (go m l) r+#endif+ -- FIXME: (2022.03.06): N.B., the benchmarks are quite erratic+ -- regarding whether this implementation is better than the+ -- 'foldMap'-with-'Endo' definition or not; whichever one is+ -- favored at some particular time is always massively favored+ -- over the other. Most recently this implementation is being+ -- favored.+ {-# INLINE foldr #-}+ foldr f z0 = \t -> go t z0 -- See [Note:FoldEta].+ where+ go Empty = id+ go (Arc _ Nothing t) = go t+ go (Arc _ (Just v) t) = f v . go t+ go (Branch _ _ l r) = go l . go r+#if MIN_VERSION_base(4,6,0)+ -- TODO: float out this definition so folks can still use it+ -- on earlier versions of base?+ {-# INLINE foldr' #-}+ foldr' f z0 = go z0 -- See [Note:FoldEta].+ where+ -- Benchmarking on GHC 9.2.1 indicates that for this function:+ -- for smaller tries, the (t,z) argument order is ~10% faster+ -- than (z,t) and allocation is the same for both; however,+ -- for larger tries the (t,z) argument order is ~6% slower+ -- than (z,t).+ --+ -- Also, weirdly, benchmarking indicates that the @($!)@+ -- in the Branch case slightly improved things.+ -- TODO: what's going on with the @($!)@; bogus?+ -- TODO: once HPC disabled, now it's saying the unflopped+ -- version without the extra @($!)@ is the faster one!+ -- (unflopped with @($!)@ is only marginally slower; probably+ -- noise).+ go !z Empty = z+ go z (Arc _ Nothing t) = go z t+ go z (Arc _ (Just v) t) = f v $! go z t+ go z (Branch _ _ l r) = go (go z r) l+#endif+ {-# INLINE foldl #-}+ foldl f z0 = \t -> go t z0 -- See [Note:FoldEta].+ where+ -- Benchmarking on GHC 9.2.1 indicates that for this function+ -- the (t,z) argument order is slightly faster (~0.8%) and+ -- allocates ~8.4% less, compared to the (z,t) order.+ -- I've no idea why the allocation would differ, especially+ -- when it doesn't for 'foldr'' and 'foldMap''.+ -- TODO: once HPC disabled, now it's showing the flopped+ -- version is ~2x faster! bogus?+ -- TODO: (2022.03.05) Rerun this benchmark on larger tries.+ go Empty z = z+ go (Arc _ Nothing t) z = go t z+ go (Arc _ (Just v) t) z = go t (f z v)+ go (Branch _ _ l r) z = go r (go l z)+#if MIN_VERSION_base(4,6,0)+ -- TODO: float out this definition so folks can still use it+ -- on earlier versions of base?+ {-# INLINE foldl' #-}+ foldl' f z0 = go z0 -- See [Note:FoldEta].+ where+ -- Benchmarking on GHC 9.2.1 indicates that for this function+ -- the (z,t) argument order is significantly faster (~10%) and+ -- allocates half as much.+ -- TODO: figure out why\/how the allocation could differ so much; bogus?+ -- TODO: figure out why benchmarking indicates the \"flop_bang\"+ -- version is ~4% faster (albeit ~32% more allocation); bogus?+ -- TODO: once HPC disabled, the flopped version is showing ~2x faster; bogus?+ -- TODO: (2022.03.05) Rerun this benchmark on larger tries.+ go !z Empty = z+ go z (Arc _ Nothing t) = go z t+ go z (Arc _ (Just v) t) = go (f z v) t+ go z (Branch _ _ l r) = go (go z l) r+#endif+ -- TODO: any point in doing foldr1,foldl1?+#if MIN_VERSION_base(4,8,0)+ -- TODO: float out this definition so folks can still use it+ -- on earlier versions of base?+ {-# INLINE length #-}+ length = size+ {-# INLINE null #-}+ null = null -- FIXME: ensure this isn't cyclic definition!+ {-# INLINE toList #-}+ toList = elems -- NB: Foldable.toList /= Trie.toList+ {-+ -- TODO: need to move these definitions here...+ -- TODO: may want to give a specialized implementation of 'member' then+ {-# INLINE elem #-} -- TODO: 'IntMap' uses INLINABLE with a local definition; rather than INLINE with the top-level definition... Also, that TLD remarks to see [Note:Closure]+ elem = member+ -}+ -- TODO: why does IntMap define these two specially, rather than using foldl' or foldl1' ?+ {-# INLINABLE maximum #-}+ maximum = go0+ where+ go0 Empty = error "Data.Foldable.maximum @Trie: empty trie"+ go0 (Arc _ Nothing t) = go0 t+ go0 (Arc _ (Just v) t) = go v t+ go0 (Branch _ _ l r) = go (go0 l) r+ go !w Empty = w+ go w (Arc _ Nothing t) = go w t+ go w (Arc _ (Just v) t) = go (max w v) t+ go w (Branch _ _ l r) = go (go w l) r+ {-# INLINABLE minimum #-}+ minimum = go0+ where+ go0 Empty = error "Data.Foldable.minimum @Trie: empty trie"+ go0 (Arc _ Nothing t) = go0 t+ go0 (Arc _ (Just v) t) = go v t+ go0 (Branch _ _ l r) = go (go0 l) r+ go !w Empty = w+ go w (Arc _ Nothing t) = go w t+ go w (Arc _ (Just v) t) = go (min w v) t+ go w (Branch _ _ l r) = go (go w l) r+ {-# INLINABLE sum #-}+ sum = F.foldl' (+) 0+ {-# INLINABLE product #-}+ product = F.foldl' (*) 1+#endif++-- TODO: newtype Keys = K Trie ; instance Foldable Keys+-- TODO: newtype Assoc = A Trie ; instance Foldable Assoc++{-----------------------------------------------------------+-- Extra folding functions+-----------------------------------------------------------}++-- TODO: be sure to keep this in sync with whatever implementation+-- choice we use for 'F.foldr'; especially since that's the one+-- method of 'Foldable' where we can't improve substantially over+-- the default implementation.+--+-- | Keyed variant of 'F.foldr'.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.2+foldrWithKey :: (ByteString -> a -> b -> b) -> b -> Trie a -> b+{-# INLINE foldrWithKey #-}+foldrWithKey f z0 = \t -> go Nil t z0 -- See [Note:FoldEta].+ where+ -- See [Note:LazyRLBS].+ go _ Empty = id+ go q (Branch _ _ l r) = go q l . go q r+ go q (Arc k Nothing t) = go (q +>! k) t+ go q (Arc k (Just v) t) = f q' v . go (fromStrict q') t+ where q' = toStrict (q +>? k)++-- TODO: probably need to benchmark these separately from the+-- non-keyed variants, since the extra recursive argument will+-- surely sway things like whether to flop or not.+-- TODO: Consider just giving an+-- <https://hackage.haskell.org/package/indexed-traversable-0.1.2/docs/Data-Foldable-WithIndex.html>+-- instance, instead of naming all these separately. That adds a+-- lot of additional dependencies just to define the class, but...+-- Or maybe give an <https://hackage.haskell.org/package/keys-3.12.3/docs/Data-Key.html>+-- instance. Again, lots of added dependencies just for the class,...+-- Then again, maybe we should just stick with doing everything+-- outside of classes; that way we could introduce a Cabal flag for+-- deciding whether the user wants either of those classes (and+-- should do the same for Witherable).++-- | Keyed variant of 'F.foldr''.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+foldrWithKey' :: (ByteString -> a -> b -> b) -> b -> Trie a -> b+{-# INLINE foldrWithKey' #-}+foldrWithKey' f z0 = go Nil z0 -- See [Note:FoldEta].+ where+ -- See [Note:LazyRLBS].+ go _ !z Empty = z+ go q z (Branch _ _ l r) = go q (go q z r) l+ go q z (Arc k Nothing t) = go (q +>! k) z t+ go q z (Arc k (Just v) t) = f q' v $! go (fromStrict q') z t+ where q' = toStrict (q +>? k)++-- | Keyed variant of 'F.foldl'.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+foldlWithKey :: (b -> ByteString -> a -> b) -> b -> Trie a -> b+{-# INLINE foldlWithKey #-}+foldlWithKey f z0 = go Nil z0 -- See [Note:FoldEta].+ where+ -- See [Note:LazyRLBS].+ go _ z Empty = z+ go q z (Branch _ _ l r) = go q (go q z l) r+ go q z (Arc k Nothing t) = go (q +>! k) z t+ go q z (Arc k (Just v) t) = go (fromStrict q') (f z q' v) t+ where q' = toStrict (q +>? k)++-- | Keyed variant of 'F.foldl''.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+foldlWithKey' :: (b -> ByteString -> a -> b) -> b -> Trie a -> b+{-# INLINE foldlWithKey' #-}+foldlWithKey' f z0 = go Nil z0 -- See [Note:FoldEta].+ where+ -- See [Note:LazyRLBS].+ go _ !z Empty = z+ go q z (Branch _ _ l r) = go q (go q z l) r+ go q z (Arc k Nothing t) = go (q +>! k) z t+ go q z (Arc k (Just v) t) = go (fromStrict q') (f z q' v) t+ where q' = toStrict (q +>? k)++-- | Catamorphism for tries. Unlike most other functions ('mapBy',+-- 'contextualMapBy', 'foldrWithKey', etc), this function does /not/+-- reconstruct the full 'ByteString' for each value; instead it+-- only returns the suffix since the previous value or branch point.+--+-- This function is a direct\/literal catamorphism of the implementation+-- datatype, erasing only some bitmasking metadata for the branches.+-- For a more semantic catamorphism, see 'cata'.+--+-- @since 0.2.6+cata_+ :: (ByteString -> Maybe a -> b -> b) -- ^ Algebra for arc.+ -> (b -> b -> b) -- ^ Algebra for binary branch.+ -> b -- ^ Algebra for empty trie.+ -> Trie a -> b+{-# INLINE cata_ #-}+cata_ a b e = go+ where+ go Empty = e+ go (Arc k mv t) = a k mv (go t)+ go (Branch _ _ l r) = b (go l) (go r)+++-- | Catamorphism for tries. Unlike most other functions ('mapBy',+-- 'contextualMapBy', 'foldrWithKey', etc), this function does /not/+-- reconstruct the full 'ByteString' for each value; instead it+-- only returns the suffix since the previous value or branch point.+--+-- This function is a semantic catamorphism; that is, it tries to+-- express the invariants of the implementation, rather than exposing+-- the literal structure of the implementation. For a more literal+-- catamorphism, see 'cata_'.+--+-- @since 0.2.6+cata+ :: (ByteString -> a -> b -> b) -- ^ Algebra for accepting arcs.+ -> (ByteString -> [b] -> b) -- ^ Algebra for n-ary branch with prefix.+ -> b -- ^ Algebra for empty trie.+ -> Trie a -> b+cata a b e = go+ where+ step k (Just v) t = a k v (go t)+ step k Nothing t = b k (collect t [])+ go Empty = e+ go (Arc k mv t) = step k mv t+ go (Branch _ _ l r) = b S.empty (collect l (collect r []))+ -- TODO: would it be profitable to use 'build' for these lists?+ collect Empty bs = bs+ collect (Arc k mv t) bs = step k mv t : bs+ collect (Branch _ _ l r) bs = collect l (collect r bs)++++{-----------------------------------------------------------+-- Instances: IsList+-----------------------------------------------------------}++#if __GLASGOW_HASKELL__ >= 708+-- |+-- __Warning__: The 'toList' method of this instance suffers+-- <Data-Trie-Internal.html#bug25 Bug #25>.+--+-- @since 0.2.7+instance GHC.Exts.IsList (Trie a) where+ type Item (Trie a) = (ByteString, a)+ fromList = fromList+ toList = toList+#endif+++-- /Moved to "Data.Trie.Internal" since 0.2.7/+-- We define this here because 'GHC.Exts.IsList' wants it.+--+-- | Convert association list into a trie. On key conflict, values+-- earlier in the list shadow later ones.+fromList :: [(ByteString,a)] -> Trie a+{-# INLINE fromList #-}+fromList = foldr (uncurry insert) empty+ where+ insert = alterBy (\_ x _ -> Just x)+++-- /Moved to "Data.Trie.Internal" since 0.2.7/+-- We define this here simply because so many instances want to use it.+-- TODO: would it be worth defining this directly, for optimizing+-- the case where list fusion doesn't eliminate the list?+--+-- | Convert trie into association list. The list is ordered+-- according to the keys.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+toList :: Trie a -> [(ByteString,a)]+{-# INLINE toList #-}+toList = toListBy (,)+++-- cf Data.ByteString.unpack+-- <http://hackage.haskell.org/packages/archive/bytestring/0.9.1.4/doc/html/src/Data-ByteString.html>+--+-- | Convert a trie into a list using a function. Resulting values+-- are in key-sorted order.+--+-- __Warning__: This function suffers <Data-Trie-Internal.html#bug25 Bug #25>.+toListBy :: (ByteString -> a -> b) -> Trie a -> [b]+{-# INLINE toListBy #-}+#if !defined(__GLASGOW_HASKELL__)+-- TODO: should probably inline foldrWithKey+-- TODO: compare performance of that vs both this and the GHC version+toListBy f t = foldrWithKey (((:) .) . f) [] t+#else+-- Written with 'build' to enable the build\/foldr fusion rules.+toListBy f t = build (toListByFB f t)++-- TODO: should probably have a specialized version for strictness,+-- and a rule to rewrite generic lazy version into it. As per+-- Data.ByteString.unpack and the comments there about strictness+-- and fusion.+toListByFB :: (ByteString -> a -> b) -> Trie a -> (b -> c -> c) -> c -> c+{-# INLINE [0] toListByFB #-}+toListByFB f t cons nil = foldrWithKey ((cons .) . f) nil t+#endif++-- /Moved to "Data.Trie.Internal" since 0.2.7/+-- So that we can do list-fusion, and reuse the definition for Foldable+--+-- | Return all values in the trie, in key-sorted order.+--+-- __Note__: Prior to version 0.2.7, this function suffered+-- <Data-Trie-Internal.html#bug25 Bug #25>; but it no longer does.+--+-- @since 0.2.2+elems :: Trie a -> [a]+{-# INLINE elems #-}+#ifdef __GLASGOW_HASKELL__+elems t = build (\cons nil -> F.foldr cons nil t)+#else+elems = F.foldr (:) []+#endif+++------------------------------------------------------------+------------------------------------------------------------+++{-----------------------------------------------------------+-- Query functions (just recurse)+-----------------------------------------------------------}++-- TODO: All the lookup-like functions for 'IntMap' remark to see+-- [Note:Closure]; so we should double check our 'lookupBy_' adheres+-- to that performance guideline.++-- | Generic function to find a value (if it exists) and the subtrie+-- rooted at the prefix. The first function argument is called if and+-- only if a node is exactly reachable by the query; if no node is+-- exactly reachable the default value is used; if the middle of+-- an arc is reached, the second function argument is used.+--+-- This function is intended for internal use. For the public-facing+-- version, see 'Data.Trie.lookupBy'.+--+-- __Note__: /Type changed in 0.2.7/+lookupBy_+ :: (a -> Trie a -> b) -- ^ The query matches a value.+ -> (Trie a -> b) -- ^ The query doesn't match, but an extension might.+ -> b -- ^ The query doesn't match, nor does any extension.+ -> ByteString -> Trie a -> b+lookupBy_ found missing clash = start+ where+ -- | Deal with epsilon query (when there is no epsilon value)+ start q t@(Branch{}) | S.null q = missing t+ start q t = go q t+ -- | The main recursion+ go _ Empty = clash+ go q (Arc k mv t) =+ let (_,k',q') = breakMaximalPrefix k q+ in case (S.null k', S.null q') of+ (False, True) -> missing (Arc k' mv t)+ (False, False) -> clash+ (True, True) ->+ case mv of+ Nothing -> missing t+ Just v -> found v t+ (True, False) -> go q' t+ go q t_@(Branch{}) = findArc t_+ where+ qh = errorLogHead "lookupBy_" q+ -- | \(\mathcal{O}(\min(m,W))\), where \(m\) is number of+ -- @Arc@s in this branching, and \(W\) is the word size of+ -- the Prefix,Mask type.+ findArc Empty = impossible "lookupBy_" -- see [Note1]+ findArc t@(Arc{}) = go q t+ findArc (Branch p m l r)+ | nomatch qh p m = clash+ | zero qh m = findArc l+ | otherwise = findArc r++-- [Note1]: Our use of the 'branch' and 'graft' smart constructors+-- ensure that 'Empty' never occurs in a 'Branch' tree ('Empty' can+-- only occur at the root, or under an 'Arc' with value); therefore+-- the @findArc Empty@ case is unreachable. If we allowed such+-- nodes, however, then this case should return the same result as+-- the 'nomatch' case.+++-- This function needs to be here, not in "Data.Trie", because of+-- 'arc' which isn't exported. We could use the monad instance+-- instead, though it'd be far more circuitous.+-- arc k Nothing t ≡ singleton k () >> t+-- arc k (Just v) t ≡ singleton k v >>= unionR t . singleton S.empty+-- (...except 'arc' doesn't do the invariant correction+-- of (>>=) for epsilon'elem't)+--+-- | Return the subtrie containing all keys beginning with a prefix.+submap :: ByteString -> Trie a -> Trie a+{-# INLINE submap #-}+submap q+ | S.null q = id+ | otherwise = lookupBy_ (Arc q . Just) (prependNN q) empty q++{-+-- TODO: would it be worth it to define this specialization? The+-- definition is nothing special; but inlining away the first three+-- arguments to 'lookupBy_' does allow to avoid any sort of dynamic+-- dispatch or closures.+lookup :: ByteString -> Trie a -> Maybe a+lookup = start+ where+ -- | Deal with epsilon query (when there is no epsilon value)+ start q t@(Branch{}) | S.null q = Nothing+ start q t = go q t+ -- | The main recursion+ go _ Empty = Nothing+ go q (Arc k mv t) =+ let (_,k',q') = breakMaximalPrefix k q+ in case (S.null k', S.null q') of+ (False, _) -> Nothing+ (True, True) -> mv+ (True, False) -> go q' t+ go q t_@(Branch{}) = findArc t_+ where+ qh = errorLogHead "lookup" q+ -- | \(\mathcal{O}(\min(m,W))\), where \(m\) is number of+ -- @Arc@s in this branching, and \(W\) is the word size of+ -- the Prefix,Mask type.+ findArc Empty = impossible "lookup" -- see [Note1]+ findArc t@(Arc{}) = go q t+ findArc (Branch p m l r)+ | nomatch qh p m = Nothing+ | zero qh m = findArc l+ | otherwise = findArc r+-}+++-- TODO: would it be worth it to have a variant like 'lookupBy_'+-- which takes the three continuations?+++-- According to our "Bench.MatchOne" benchmark, this is in fact+-- much faster than using 'matches_' and relying on list fusion.+--+-- | Given a query, find the longest prefix with an associated value+-- in the trie, returning the length of that prefix and the associated+-- value.+--+-- This function may not have the most useful return type. For a+-- version that returns the prefix itself as well as the remaining+-- string, see 'Data.Trie.match'.+--+-- @since 0.2.4+match_ :: Trie a -> ByteString -> Maybe (Int, a)+match_ = flip start+ where+ -- | Deal with epsilon query (when there is no epsilon value)+ start q (Branch{}) | S.null q = Nothing+ start q t = match1 0 q t+ -- TODO: for the non-null Branch case, maybe we should jump directly to 'findArc' (i.e., inline that case of 'match1')+ -- | Find the first match, or return Nothing if there isn't one.+ match1 _ _ Empty = Nothing+ match1 n q (Arc k mv t) =+ let (p,k',q') = breakMaximalPrefix k q+ !n' = n + S.length p+ in case (S.null k', S.null q') of+ (False, _) -> Nothing+ (True, True) -> (,) n' <$> mv+ (True, False) ->+ case mv of+ Nothing -> match1 n' q' t+ Just v -> matchN n' v n' q' t+ match1 n q t_@(Branch{}) = findArc t_+ where+ qh = errorLogHead "match_" q+ -- | \(\mathcal{O}(\min(m,W))\), where \(m\) is number of+ -- @Arc@s in this branching, and \(W\) is the word size of+ -- the Prefix,Mask type.+ findArc Empty = impossible "match_" -- see [Note1]+ findArc t@(Arc{}) = match1 n q t+ findArc (Branch p m l r)+ | nomatch qh p m = Nothing+ | zero qh m = findArc l+ | otherwise = findArc r+ -- | Find the next match, or return the previous one if there are no more.+ matchN n0 v0 _ _ Empty = Just (n0,v0)+ matchN n0 v0 n q (Arc k mv t) =+ let (p,k',q') = breakMaximalPrefix k q+ !n' = n + S.length p+ in case (S.null k', S.null q') of+ (False, _) -> Just (n0,v0)+ (True, True) ->+ case mv of+ Nothing -> Just (n0,v0)+ Just v -> Just (n',v)+ (True, False) ->+ case mv of+ Nothing -> matchN n0 v0 n' q' t+ Just v -> matchN n' v n' q' t+ matchN n0 v0 n q t_@(Branch{}) = findArc t_+ where+ qh = errorLogHead "match_" q+ -- | \(\mathcal{O}(\min(m,W))\), where \(m\) is number of+ -- @Arc@s in this branching, and \(W\) is the word size of+ -- the Prefix,Mask type.+ findArc Empty = impossible "match_" -- see [Note1]+ findArc t@(Arc{}) = matchN n0 v0 n q t+ findArc (Branch p m l r)+ | nomatch qh p m = Just (n0,v0)+ | zero qh m = findArc l+ | otherwise = findArc r+++-- | Given a query, find all prefixes with associated values in the+-- trie, and return the length of each prefix with their value, in+-- order from shortest prefix to longest. This function is a good+-- producer for list fusion.+--+-- This function may not have the most useful return type. For a+-- version that returns the prefix itself as well as the remaining+-- string, see 'Data.Trie.matches'.+--+-- @since 0.2.4+matches_ :: Trie a -> ByteString -> [(Int,a)]+matches_ t q =+#if !defined(__GLASGOW_HASKELL__)+ matchFB_ t q (((:) .) . (,)) []+#else+ build (\cons nil -> matchFB_ t q ((cons .) . (,)) nil)+{-# INLINE matches_ #-}+#endif++matchFB_ :: Trie a -> ByteString -> (Int -> a -> r -> r) -> r -> r+matchFB_ = \t q cons nil -> matchFB_' cons q t nil+ where+ matchFB_' cons = start+ where+ -- | Deal with epsilon query (when there is no epsilon value)+ start q (Branch{}) | S.null q = id+ start q t = go 0 q t++ -- | The main recursion+ go _ _ Empty = id+ go n q (Arc k mv t) =+ let (p,k',q') = breakMaximalPrefix k q+ !n' = n + S.length p+ in if S.null k'+ then+ case mv of { Nothing -> id; Just v -> cons n' v}+ .+ if S.null q' then id else go n' q' t+ else id+ go n q t_@(Branch{}) = findArc t_+ where+ qh = errorLogHead "matches_" q+ -- | \(\mathcal{O}(\min(m,W))\), where \(m\) is number+ -- of @Arc@s in this branching, and \(W\) is the word+ -- size of the Prefix,Mask type.+ findArc Empty = impossible "matches_" -- see [Note1]+ findArc t@(Arc{}) = go n q t+ findArc (Branch p m l r)+ | nomatch qh p m = id+ | zero qh m = findArc l+ | otherwise = findArc r+++{-----------------------------------------------------------+-- Simple modification functions (recurse and clone spine)+-----------------------------------------------------------}++-- TODO: We should CPS on Empty to avoid cloning spine if no change.+-- Difficulties arise with the calls to 'branch' and 'arc'. Will+-- have to create a continuation chain, so no savings on memory+-- allocation; but would have savings on held memory, if they're+-- still holding the old one...+--+-- | Generic function to alter a trie by one element with a function+-- to resolve conflicts (or non-conflicts).+alterBy :: (ByteString -> a -> Maybe a -> Maybe a)+ -> ByteString -> a -> Trie a -> Trie a+alterBy f q x = alterBy_ (\mv t -> (f q x mv, t)) q+-- TODO: use GHC's 'inline' function so that this gets specialized away.+-- TODO: benchmark to be sure that this doesn't introduce unforseen+-- performance costs because of the uncurrying etc.+-- TODO: move to "Data.Trie" itself instead of here, since it doesn't+-- depend on any internals (unless we actually do the CPS optimization).+-- TODO: would there be any benefit in basing this off a different+-- function that captures the invariant that the subtrie is left+-- alone?+++-- Not susceptible to [bug26] because it can only delete a single value\/subtrie.+--+-- | A variant of 'alterBy' which also allows modifying the sub-trie.+-- If the function returns @(Just v, t)@ and @lookup 'S.empty' t == Just w@,+-- then the @w@ will be overwritten by @v@.+--+-- @since 0.2.3+-- __Note__: /Type changed in 0.2.6/+alterBy_+ :: (Maybe a -> Trie a -> (Maybe a, Trie a))+ -> ByteString -> Trie a -> Trie a+alterBy_ f = start+ where+ start q t | not (S.null q) = go q t+ start _ (Arc k mv s) | S.null k = mayEpsilon $$ f mv s+ start _ t = mayEpsilon $$ f Nothing t++ -- @go@ is always called with non-null @q@, therefore @nothing@ is too.+ nothing q = arcNN q $$ f Nothing Empty++ go q Empty = nothing q+ go q t@(Branch p m l r)+ | nomatch qh p m =+ case nothing q of+ Empty -> t+ s -> graft p t qh s+ | zero qh m = branchL p m (go q l) r+ | otherwise = branchR p m l (go q r)+ where qh = errorLogHead "alterBy_" q+ go q t@(Arc k mv s) =+ let (p,k',q') = breakMaximalPrefix k q in+ case (S.null k', S.null q') of+ (False, True) -> -- add node to middle of Arc+ arcNN p $$ f Nothing (Arc k' mv s)+ (False, False) ->+ case nothing q' of+ Empty -> t -- Nothing to add, reuse old Arc+ Branch{} -> impossible "alterBy_" -- 'arcNN' can't Branch+ l@(Arc{}) ->+ -- Inlined version of @prepend p@, capturing the+ -- invariant that the 'graft' must be a @Branch@.+ (if S.null p then id else Arc p Nothing)+ -- 'arcNN' will always have that the string in @l@+ -- must begin with @q'@, which is non-null here and+ -- therefore @arcPrefix q'@ is equivalent to taking+ -- the 'arcPrefix' of the string in @l@.+ $ graft (arcPrefix q') l (arcPrefix k') (Arc k' mv s)+ (True, True) -> arcNN k $$ f mv s+ (True, False) -> arcNN k mv (go q' s)+++-- TODO: benchmark vs the definition with alterBy\/liftM+-- TODO: add a variant that's strict in the function.+--+-- /Since: 0.2.6/ for being exported from "Data.Trie.Internal"+-- rather than "Data.Trie"+--+-- | Apply a function to the value at a key. If the key is not+-- present, then the trie is returned unaltered.+adjust :: (a -> a) -> ByteString -> Trie a -> Trie a+adjust f = start+ where+ start q t | not (S.null q) = go q t+ start _ (Arc k (Just v) t) | S.null k = Arc k (Just (f v)) t+ start _ t = t++ go _ Empty = Empty+ go q t@(Branch p m l r)+ | nomatch qh p m = t+ | zero qh m = Branch p m (go q l) r+ | otherwise = Branch p m l (go q r)+ where qh = errorLogHead "adjust" q+ go q t@(Arc k mv s) =+ let (_,k',q') = breakMaximalPrefix k q in+ case (S.null k', S.null q') of+ (False, _) -> t+ (True, True) -> Arc k (f <$> mv) s+ (True, False) -> Arc k mv (go q' s)+++{-----------------------------------------------------------+-- Trie-combining functions+-----------------------------------------------------------}++-- TODO: it may be helpful to have a version of 'mergeBy' where the+-- function doesn't return 'Maybe' (i.e., 'Data.Trie.Convenience.unionWith');+-- because knowing we can't delete elements would allow to use true+-- constructors directly, rather than smart constructors that patch+-- up the deletion cases. Especially since the vast majority of+-- our own uses of 'mergeBy' fall into this category.++-- Not susceptible to [bug26] because it doesn't delete any values.+--+-- Alas, benchmarking indicates that this gives only a very trivial+-- benefit over 'TC.unionWith' as implemented via 'mergeBy'.+--+-- | Take the union of two tries, using a function to resolve+-- conflicts. The resulting trie is constructed strictly, but the+-- results of the combining function are evaluated lazily.+wip_unionWith :: (a -> a -> a) -> Trie a -> Trie a -> Trie a+wip_unionWith f = start+ where+ -- | Deals with epsilon entries, before recursing into @go@+ -- TODO: for all of these, add assertions that null bytestring entails must be Just; instead of pattern matching on it directly.+ start (Arc k0 (Just v0) s0) (Arc k1 (Just v1) s1) | S.null k0 && S.null k1+ = epsilon (f v0 v1) (go s0 s1)+ start (Arc k0 (Just v0) s0) t1 | S.null k0 = epsilon v0 (go s0 t1)+ start t0 (Arc k1 (Just v1) s1) | S.null k1 = epsilon v1 (go t0 s1)+ start t0 t1 = go t0 t1++ -- | The main recursion+ go Empty t1 = t1+ go t0 Empty = t0+ -- \(\mathcal{O}(n+m)\) for this part where \(n\) and \(m\) are+ -- sizes of the branchings.+ go t0@(Branch p0 m0 l0 r0)+ t1@(Branch p1 m1 l1 r1)+ | shorter m0 m1 = union0+ | shorter m1 m0 = union1+ | p0 == p1 = Branch p0 m0 (go l0 l1) (go r0 r1)+ | otherwise = graft p0 t0 p1 t1+ where+ union0 | nomatch p1 p0 m0 = graft p0 t0 p1 t1+ | zero p1 m0 = Branch p0 m0 (go l0 t1) r0+ | otherwise = Branch p0 m0 l0 (go r0 t1)+ union1 | nomatch p0 p1 m1 = graft p0 t0 p1 t1+ | zero p0 m1 = Branch p1 m1 (go t0 l1) r1+ | otherwise = Branch p1 m1 l1 (go t0 r1)+ --+ go t0@(Arc k0 mv0 s0)+ t1@(Arc k1 mv1 s1)+ = arcMerge k0 t0 k1 t1 $ \ pre k0' k1' ->+ let {-# INLINE t0' #-}+ t0' = Arc k0' mv0 s0+ {-# INLINE t1' #-}+ t1' = Arc k1' mv1 s1+ in+ case (S.null k0', S.null k1') of+ (True, True) -> arcNN pre (mergeMaybe (\v0 v1 -> Just (f v0 v1)) mv0 mv1) (go s0 s1) -- TODO: if both arcs are reject, then both @s0,s1@ are branches so we can simplify the 'arcNN' to avoid the case analysis in 'prependNN'.+ (True, False) -> Arc pre mv0 (go s0 t1')+ (False,True) -> Arc pre mv1 (go t0' s1)+ (False,False) -> wye pre k0' t0' k1' t1'+ go t0@(Arc k0 _ _)+ t1@(Branch p1 m1 l r)+ | nomatch p0 p1 m1 = graft p1 t1 p0 t0+ | zero p0 m1 = Branch p1 m1 (go t0 l) r+ | otherwise = Branch p1 m1 l (go t0 r)+ where p0 = arcPrefix k0+ go t0@(Branch p0 m0 l r)+ t1@(Arc k1 _ _)+ | nomatch p1 p0 m0 = graft p0 t0 p1 t1+ | zero p1 m0 = Branch p0 m0 (go l t1) r+ | otherwise = Branch p0 m0 l (go r t1)+ where p1 = arcPrefix k1+++-- FIXME: See [bug26].+-- TEST CASES: foldr (unionL . uncurry singleton) empty t+-- foldr (uncurry insert) empty t+-- where t = map (\s -> (pk s, 0))+-- ["heat","hello","hoi","apple","appa","hell","appb","appc"]+--+-- | Take the union of two tries, using a function to resolve collisions.+-- This can only define the space of functions between union and+-- symmetric difference but, with those two, all set operations can+-- be defined (albeit inefficiently).+mergeBy :: (a -> a -> Maybe a) -> Trie a -> Trie a -> Trie a+mergeBy f = start+ where+ -- | Deals with epsilon entries, before recursing into @go@+ -- TODO: for all of these, add assertions that null bytestring entails must be Just; instead of pattern matching on it directly.+ start (Arc k0 (Just v0) s0) (Arc k1 (Just v1) s1) | S.null k0 && S.null k1+ = mayEpsilon (f v0 v1) (go s0 s1)+ start (Arc k0 (Just v0) s0) t1 | S.null k0 = epsilon v0 (go s0 t1)+ start t0 (Arc k1 (Just v1) s1) | S.null k1 = epsilon v1 (go t0 s1)+ start t0 t1 = go t0 t1++ -- | The main recursion+ go Empty t1 = t1+ go t0 Empty = t0+ -- \(\mathcal{O}(n+m)\) for this part where \(n\) and \(m\) are+ -- sizes of the branchings.+ go t0@(Branch p0 m0 l0 r0)+ t1@(Branch p1 m1 l1 r1)+ | shorter m0 m1 = union0+ | shorter m1 m0 = union1+ | p0 == p1 = branch p0 m0 (go l0 l1) (go r0 r1)+ | otherwise = graft p0 t0 p1 t1+ where+ union0 | nomatch p1 p0 m0 = graft p0 t0 p1 t1+ | zero p1 m0 = branchL p0 m0 (go l0 t1) r0+ | otherwise = branchR p0 m0 l0 (go r0 t1)+ union1 | nomatch p0 p1 m1 = graft p0 t0 p1 t1+ | zero p0 m1 = branchL p1 m1 (go t0 l1) r1+ | otherwise = branchR p1 m1 l1 (go t0 r1)+ --+ go t0@(Arc k0 mv0 s0)+ t1@(Arc k1 mv1 s1)+ = arcMerge k0 t0 k1 t1 $ \ pre k0' k1' ->+ let {-# INLINE t0' #-}+ t0' = Arc k0' mv0 s0+ {-# INLINE t1' #-}+ t1' = Arc k1' mv1 s1+ in+ -- TODO: can be smarter than 'arcNN' here...+ case (S.null k0', S.null k1') of+ (True, True) -> arcNN pre (mergeMaybe f mv0 mv1) (go s0 s1)+ (True, False) -> arcNN pre mv0 (go s0 t1')+ (False,True) -> arcNN pre mv1 (go t0' s1)+ (False,False) -> wye pre k0' t0' k1' t1'+ go t0@(Arc k0 _ _)+ t1@(Branch p1 m1 l r)+ | nomatch p0 p1 m1 = graft p1 t1 p0 t0+ | zero p0 m1 = branchL p1 m1 (go t0 l) r+ | otherwise = branchR p1 m1 l (go t0 r)+ where p0 = arcPrefix k0+ go t0@(Branch p0 m0 l r)+ t1@(Arc k1 _ _)+ | nomatch p1 p0 m0 = graft p0 t0 p1 t1+ | zero p1 m0 = branchL p0 m0 (go l t1) r+ | otherwise = branchR p0 m0 l (go r t1)+ where p1 = arcPrefix k1+++mergeMaybe :: (a -> a -> Maybe a) -> Maybe a -> Maybe a -> Maybe a+{-# INLINE mergeMaybe #-}+mergeMaybe _ Nothing Nothing = Nothing+mergeMaybe _ Nothing mv1@(Just _) = mv1+mergeMaybe _ mv0@(Just _) Nothing = mv0+mergeMaybe f (Just v0) (Just v1) = f v0 v1+++-- FIXME: See [bug26].+-- | Take the intersection of two tries, using a function to resolve+-- collisions.+--+-- @since 0.2.6+intersectBy :: (a -> b -> Maybe c) -> Trie a -> Trie b -> Trie c+intersectBy f = start+ where+ -- | Deals with epsilon entries, before recursing into @go@+ start (Arc k0 mv0 s0) (Arc k1 mv1 s1) | S.null k0 && S.null k1+ = mayEpsilon (intersectMaybe f mv0 mv1) (go s0 s1)+ start (Arc k0 (Just _) s0) t1 | S.null k0 = go s0 t1+ start t0 (Arc k1 (Just _) s1) | S.null k1 = go t0 s1+ start t0 t1 = go t0 t1++ -- | The main recursion+ go Empty _ = Empty+ go _ Empty = Empty+ go t0@(Branch p0 m0 l0 r0)+ t1@(Branch p1 m1 l1 r1)+ | shorter m0 m1 = isect0+ | shorter m1 m0 = isect1+ | p0 == p1 = branch p0 m0 (go l0 l1) (go r0 r1)+ | otherwise = Empty+ where+ isect0 | nomatch p1 p0 m0 = Empty+ | zero p1 m0 = go l0 t1+ | otherwise = go r0 t1+ isect1 | nomatch p0 p1 m1 = Empty+ | zero p0 m1 = go t0 l1+ | otherwise = go t0 r1+ go (Arc k0 mv0 s0)+ (Arc k1 mv1 s1)+ -- We can simplify 'getMask' to 'xor' here, avoiding the+ -- cost of the 'highestBitMask'; because we don't care about+ -- the actual mask itself, just the nonzero-ness.+ | xor (arcPrefix k0) (arcPrefix k1) /= 0 = Empty+ | otherwise =+ let (pre,k0',k1') = breakMaximalPrefix k0 k1 in+ if S.null pre+ then error "intersectBy: no mask, but no prefix string"+ else+ let {-# INLINE t0' #-}+ t0' = Arc k0' mv0 s0+ {-# INLINE t1' #-}+ t1' = Arc k1' mv1 s1+ in+ -- TODO: be smarter about the recursion and 'prependNN'+ case (S.null k0', S.null k1') of+ (True, True) -> arcNN pre (intersectMaybe f mv0 mv1) (go s0 s1)+ (True, False) -> prependNN pre (go s0 t1')+ (False,True) -> prependNN pre (go t0' s1)+ (False,False) -> prependNN pre (go t0' t1')+ go t0@(Arc k0 _ _)+ (Branch p1 m1 l r)+ | nomatch p0 p1 m1 = Empty+ | zero p0 m1 = go t0 l+ | otherwise = go t0 r+ where p0 = arcPrefix k0+ go (Branch p0 m0 l r)+ t1@(Arc k1 _ _)+ | nomatch p1 p0 m0 = Empty+ | zero p1 m0 = go l t1+ | otherwise = go r t1+ where p1 = arcPrefix k1+++intersectMaybe :: (a -> b -> Maybe c) -> Maybe a -> Maybe b -> Maybe c+{-# INLINE intersectMaybe #-}+intersectMaybe f (Just v0) (Just v1) = f v0 v1+intersectMaybe _ _ _ = Nothing+++-- TODO(github#23): add 'differenceBy'+++{-----------------------------------------------------------+-- Priority-queue functions+-----------------------------------------------------------}+-- TODO: should verify that all of these are now free of the quadratic+-- slowdown from reconstructing keys. They should be, but just to+-- verify that some new quadratic hasn't accidentally crept in...++-- TODO: Maybe we should follow 'IntMap' and separate out NOINLINE+-- versions of these functions which construct a specialized View+-- type, vs INLINE wrappers that convert the View into the+-- Maybe/pair/etc.++-- | Return the lexicographically smallest 'ByteString' and the+-- value it's mapped to; or 'Nothing' for the empty trie. When one+-- entry is a prefix of another, the prefix will be returned.+--+-- __Note__: Prior to version 0.2.7, this function suffered+-- <Data-Trie-Internal.html#bug25 Bug #25>; but it no longer does.+--+-- @since 0.2.2+minAssoc :: Trie a -> Maybe (ByteString, a)+minAssoc = go Nil+ where+ go !_ Empty = Nothing+ go q (Arc k (Just v) _) = Just (toStrict (q +>? k), v)+ go q (Arc k Nothing t) = go (q +>! k) t+ go q (Branch _ _ l _) = go q l+++-- | Return the lexicographically largest 'ByteString' and the+-- value it's mapped to; or 'Nothing' for the empty trie. When one+-- entry is a prefix of another, the longer one will be returned.+--+-- __Note__: Prior to version 0.2.7, this function suffered+-- <Data-Trie-Internal.html#bug25 Bug #25>; but it no longer does.+--+-- @since 0.2.2+maxAssoc :: Trie a -> Maybe (ByteString, a)+maxAssoc = go Nil+ where+ go !_ Empty = Nothing+ go q (Arc k (Just v) Empty) = Just (toStrict (q +>? k), v)+ go q (Arc k (Just _) t) = go (q +>? k) t+ go q (Arc k Nothing t) = go (q +>! k) t+ go q (Branch _ _ _ r) = go q r+++mapView :: (Trie a -> Trie a)+ -> Maybe (ByteString, a, Trie a) -> Maybe (ByteString, a, Trie a)+{-# INLINE mapView #-}+mapView _ Nothing = Nothing+mapView f (Just (k,v,t)) = Just (k,v, f t)+++-- Not susceptible to [bug26] because it can only delete a single value.+--+-- | Update the 'minAssoc' and return the old 'minAssoc'.+--+-- __Note__: Prior to version 0.2.7, this function suffered+-- <Data-Trie-Internal.html#bug25 Bug #25>; but it no longer does.+--+-- @since 0.2.2+updateMinViewBy :: (ByteString -> a -> Maybe a)+ -> Trie a -> Maybe (ByteString, a, Trie a)+updateMinViewBy f = go Nil+ where+ go !_ Empty = Nothing+ go q (Arc k (Just v) t) = let q' = toStrict (q +>? k)+ in Just (q',v, arc k (f q' v) t)+ go q (Arc k Nothing t) = mapView (prependNN k) (go (q +>! k) t)+ go q (Branch p m l r) = mapView (\l' -> branchL p m l' r) (go q l)+++-- Not susceptible to [bug26] because it can only delete a single value.+--+-- | Update the 'maxAssoc' and return the old 'maxAssoc'.+--+-- __Note__: Prior to version 0.2.7, this function suffered+-- <Data-Trie-Internal.html#bug25 Bug #25>; but it no longer does.+--+-- @since 0.2.2+updateMaxViewBy :: (ByteString -> a -> Maybe a)+ -> Trie a -> Maybe (ByteString, a, Trie a)+updateMaxViewBy f = go Nil+ where+ go !_ Empty = Nothing+ go q (Arc k (Just v) Empty) = let q' = toStrict (q +>? k)+ in Just (q',v, arc k (f q' v) Empty)+ go q (Arc k mv@(Just _) t) = mapView (Arc k mv) (go (q +>? k) t)+ go q (Arc k Nothing t) = mapView (prepend k) (go (q +>! k) t)+ go q (Branch p m l r) = mapView (branchR p m l) (go q r) ------------------------------------------------------------ ------------------------------------------------------- fin.
+ src/Data/Trie/Internal/BitTwiddle.hs view
@@ -0,0 +1,258 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs -fno-warn-name-shadowing #-}+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE CPP #-}+#ifdef __GLASGOW_HASKELL__+{-# LANGUAGE MagicHash #-}+#endif+#if __GLASGOW_HASKELL__ >= 701+-- Naturally, the MagicHash stuff from "GHC.Exts" isn't considered safe.+{-# LANGUAGE Trustworthy #-}+#endif+----------------------------------------------------------------+-- ~ 2021.12.14+-- |+-- Module : Data.Trie.Internal.BitTwiddle+-- Copyright : 2012 Clark Gaebel, 2012 Johan Tibel, 2002 Daan Leijen+-- License : BSD-3-Clause+-- Maintainer : libraries@haskell.org, wren@cpan.org+-- Stability : stable+-- Portability : portable (with CPP)+--+-- Functions to treat 'Word' as a bit-vector for big-endian patricia+-- trees. This code is duplicated from "Data.IntMap" (or+-- "Utils.Containers.Internal.BitUtil" these days). The only+-- differences are that some of the conversion functions are+-- specialized to 'Data.Word.Word8' for bytestrings, instead of+-- being specialized to 'Int'.+----------------------------------------------------------------++module Data.Trie.Internal.BitTwiddle+ (+ -- * Type aliases+ KeyElem, Prefix, Mask+ -- * Predicates+ , zero, nomatch, shorter+ -- * Constructors+ , applyMask, getMask+ ) where++import Data.Trie.Internal.ByteString (ByteStringElem)++-- It's too much noise to fully restrict this import, so just note+-- the requirements:+-- base 4.8.0 / GHC 7.10.1 -- 'countLeadingZeros', 'countTrailingZeros'+-- base 4.7.0 / GHC 7.8.2 -- 'FiniteBits', 'finiteBitSize'+-- base 4.5.0 / GHC 7.4.1 -- 'popCount'+import Data.Bits++-- To make it clearer what we're really testing for.+-- TODO: make this into a Cabal flag; for easier testing if nothing else.+#define USE_CLZ_IMPLEMENTATION MIN_VERSION_base(4,8,0)++#if __GLASGOW_HASKELL__ >= 503+-- Before GHC 5.3 these were in "GlaExts" instead.+import GHC.Exts+ ( Word(W#)+ , Int(I#)+# if USE_CLZ_IMPLEMENTATION+ , shiftL#+# else+ , shiftRL#+# endif+ )+#else+import Data.Word (Word)+#endif++----------------------------------------------------------------++-- | 'KeyElem' is what we actually use for 'Prefix' and 'Mask'.+-- For now we're using 'ByteStringElem' ('Data.Word.Word8') for+-- simplicity, but in the future we might switch to a larger word+-- size.+type KeyElem = ByteStringElem++-- | Some prefix of the 'KeyElem', as constructed by 'applyMask'.+type Prefix = KeyElem++-- | A single bit, signifying a mask (of all the bits preceding the+-- masking bit).+type Mask = KeyElem++elemToNat :: KeyElem -> Word+{-# INLINE elemToNat #-}+elemToNat = fromIntegral++natToElem :: Word -> KeyElem+{-# INLINE natToElem #-}+natToElem = fromIntegral++-- TODO: newer versions of the containers library just use+-- 'unsafeShift{R,L}' unilaterally. So, what is the difference+-- (i.e., these days) between using the 'uncheckedShift{L,RL}#' of+-- 'unsafeShift{L,R}' vs using the 'shift{L,RL}#' of 'shift{L,R}'?+-- Also, do we no longer need to trick GHC into actually unboxing+-- and inlining these?+#if USE_CLZ_IMPLEMENTATION+shiftLL :: Word -> Int -> Word+{-# INLINE shiftLL #-}+# if __GLASGOW_HASKELL__+-- Use unboxing to get @shiftLL@ inlined.+shiftLL (W# x) (I# i) = W# (shiftL# x i)+# else+shiftLL x i = unsafeShiftL x i+# endif+#else+shiftRL :: Word -> Int -> Word+{-# INLINE shiftRL #-}+# if __GLASGOW_HASKELL__+-- Use unboxing to get @shiftRL@ inlined.+shiftRL (W# x) (I# i) = W# (shiftRL# x i)+# else+shiftRL x i = unsafeShiftR x i+# endif+#endif+++{---------------------------------------------------------------+-- Endian independent bit twiddling (Trie endianness, not architecture)+---------------------------------------------------------------}++-- TODO: should we use the (Bits Word8) instance instead of 'elemToNat'+-- and (Bits Nat)? We need to compare Core, C--, or ASM in order+-- to decide this. The choice will apply to 'zero', 'applyMask', 'maskW',...+-- If we shouldn't, then we should probably send a patch upstream+-- to fix the (Bits Word8) instance.++-- | Is the key zero under the masking bit? If true then whatever+-- is associated with that key should go to the left, otherwise it+-- should go to the right.+zero :: KeyElem -> Mask -> Bool+{-# INLINE zero #-}+zero i m = (elemToNat i) .&. (elemToNat m) == 0++-- | Does the masked key /not/ match the prefix? (Hence a subtree+-- matching the value doesn't exist.)+nomatch :: KeyElem -> Prefix -> Mask -> Bool+{-# INLINE nomatch #-}+nomatch i p m = applyMask i m /= p++-- | Convert a masking bit to the full mask it represents, and then+-- return the prefix of the key under that mask (i.e., all the bits+-- preceding the masking bit).+applyMask :: KeyElem -> Mask -> Prefix+{-# INLINE applyMask #-}+applyMask i m = maskW (elemToNat i) (elemToNat m)+++{---------------------------------------------------------------+-- Big endian operations (Trie endianness, not architecture)+---------------------------------------------------------------}++-- | Get mask by setting all bits higher than the smallest bit in+-- @m@. Then apply that mask to @i@.+maskW :: Word -> Word -> Prefix+{-# INLINE maskW #-}+maskW i m = natToElem (i .&. (complement (m-1) `xor` m))+-- TODO: try the alternatives mentioned in the Containers paper:+-- \i m -> natToElem (i .&. (negate m - m))+-- \i m -> natToElem (i .&. (m * complement 1))+-- N.B. these return /all/ the low bits, and therefore they are not+-- equal functions for all m. They are, however, equal when only+-- one bit of m is set.++-- | Determine whether the first mask denotes a shorter prefix than+-- the second.+shorter :: Mask -> Mask -> Bool+{-# INLINE shorter #-}+shorter m1 m2 = elemToNat m1 > elemToNat m2++-- | Determine first differing bit of two prefixes.+getMask :: Prefix -> Prefix -> Mask+{-# INLINE getMask #-}+getMask p1 p2 = natToElem (highestBitMask (elemToNat p1 `xor` elemToNat p2))++{---------------------------------------------------------------+ Finding the highest bit (mask) in a word [x] can be done efficiently+ in three ways:+ * convert to a floating point value and the mantissa tells us the+ [log2(x)] that corresponds with the highest bit position. The+ mantissa is retrieved either via the standard C function [frexp]+ or by some bit twiddling on IEEE compatible numbers (float).+ Note that one needs to use at least [double] precision for an+ accurate mantissa of 32 bit numbers.+ * use bit twiddling, a logarithmic sequence of bitwise or's and+ shifts (bit).+ * use processor specific assembler instruction (asm).++ The most portable way would be [bit], but is it efficient enough?+ I have measured the cycle counts of the different methods on an+ AMD Athlon-XP 1800 (~ Pentium III 1.8Ghz) using the RDTSC+ instruction:++ highestBitMask: method cycles+ --------------+ frexp 200+ float 33+ bit 11+ asm 12++ highestBit: method cycles+ --------------+ frexp 195+ float 33+ bit 11+ asm 11++ Wow, the bit twiddling is on today's RISC like machines even+ faster than a single CISC instruction (BSR)!+---------------------------------------------------------------}++{---------------------------------------------------------------+ [highestBitMask] returns a word where only the highest bit is+ set. It is found by first setting all bits in lower positions+ than the highest bit and than taking an exclusive or with the+ original value. Allthough the function may look expensive, GHC+ compiles this into excellent C code that subsequently compiled+ into highly efficient machine code. The algorithm is derived from+ Jorg Arndt's FXT library.+---------------------------------------------------------------}++highestBitMask :: Word -> Word+{-# INLINE highestBitMask #-}+#if USE_CLZ_IMPLEMENTATION+-- This is the implementation used in newer versions of the containers library.+-- Added this implementation here in version 0.2.7.+highestBitMask w = shiftLL 1 (wordSize - 1 - countLeadingZeros w)+#else+-- This is the classic one we used up to bytestring-trie-0.2.6.1+-- And it's still what containers falls back to for older versions of base.+--+-- N.B., because this is not exported and is only used by 'branchMask'+-- which operates on 'Word8' inputs, we can safely restrict the+-- algorithm to only doing the first few steps, rather than doing+-- all the steps needed for 'Word64'.+highestBitMask x+ = case (x .|. shiftRL x 1) of+ x -> case (x .|. shiftRL x 2) of+ x -> case (x .|. shiftRL x 4) of -- for 8-bit input range.+ {-+ x -> case (x .|. shiftRL x 8) of -- for 16-bit+ x -> case (x .|. shiftRL x 16) of -- for 32-bit+ x -> case (x .|. shiftRL x 32) of -- for 64-bit platforms+ -}+ x -> (x `xor` shiftRL x 1)+#endif++#if USE_CLZ_IMPLEMENTATION+wordSize :: Int+{-# INLINE wordSize #-}+# if MIN_VERSION_base(4,7,0)+wordSize = finiteBitSize (0 :: Word)+# else+wordSize = bitSize (0 :: Word)+# endif+#endif++----------------------------------------------------------------+----------------------------------------------------------- fin.
+ src/Data/Trie/Internal/ByteString.hs view
@@ -0,0 +1,346 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE CPP, BangPatterns #-}+#if __GLASGOW_HASKELL__ >= 701+-- Neither 'unsafeDupablePerformIO' nor 'Data.ByteString.Internal' is safe.+{-# LANGUAGE Trustworthy #-}+#endif+------------------------------------------------------------+-- ~ 2025-02-11+-- |+-- Module : Data.Trie.Internal.ByteString+-- Copyright : 2008--2025 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : stable+-- Portability : GHC-only+--+-- Helper functions on 'ByteString's for "Data.Trie.Internal".+------------------------------------------------------------++module Data.Trie.Internal.ByteString+ ( ByteString, ByteStringElem+ , breakMaximalPrefix+ , RevLazyByteString(..), (+>!), (+>?), fromStrict, toStrict+ -- TODO: we want to export the 'Nil' constructor; but+ -- do we want to export the patterns?+ ) where++import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S+#if MIN_VERSION_bytestring(0,11,5)+import qualified Foreign.Marshal.Utils as FMU+#endif+import Data.ByteString.Internal (ByteString(PS))+import Data.Word+import Foreign.ForeignPtr (ForeignPtr)+#if MIN_VERSION_base(4,15,0)+-- [aka GHC 9.0.1]:+import GHC.ForeignPtr (unsafeWithForeignPtr)+#else+import Foreign.ForeignPtr (withForeignPtr)+#endif+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (Storable(..))+-- This module name is since @__GLASGOW_HASKELL__ >= 611@.+import GHC.IO (unsafeDupablePerformIO)++------------------------------------------------------------+#if !(MIN_VERSION_base(4,15,0))+-- bytestring-0.10.12.1 and 0.11.1.0 use and export this definition;+-- however neither 0.10.12.0 nor 0.11.0.0 define nor use it.So,+-- rather than dealing with all that nonsense, we'll just do it+-- ourselves.+unsafeWithForeignPtr :: ForeignPtr a -> (Ptr a -> IO b) -> IO b+unsafeWithForeignPtr = withForeignPtr+#endif++memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()+#if MIN_VERSION_bytestring(0,11,5)+-- This is the version when 'S.memcpy' got deprecated, though it+-- still exists at least through 0.12.2.0.+memcpy = FMU.copyBytes+#else+memcpy = S.memcpy+#endif++------------------------------------------------------------+------------------------------------------------------------+-- | Associated type of 'ByteString'+type ByteStringElem = Word8+++------------------------------------------------------------+-- The @since annotation is for when this got re-exported from+-- "Data.Trie.Internal".+--+-- | Returns the longest shared prefix and the two remaining suffixes+-- for a pair of strings. This function performs no allocation\/copying,+-- it simply returns slices\/views of the arguments.+--+-- * @s ≡ (\\(pre,s',z') -> pre '<>' s') ('breakMaximalPrefix' s z)@+-- * @z ≡ (\\(pre,s',z') -> pre '<>' z') ('breakMaximalPrefix' s z)@+--+-- @since 0.2.2+breakMaximalPrefix+ :: ByteString+ -> ByteString+ -> (ByteString, ByteString, ByteString)+--+-- [Implementation Notes]+--+-- * We've had to define 'strictTriple' and use BangPatterns to+-- keep GHC from wrapping all the returned triples in+-- ghc-prim:'GHC.Magic.lazy'. Not sure how much this actually+-- helps performance, but it's a stepping stone towards defining+-- a custom result type which unpacks the three ByteStrings. And+-- given that GHC's worker-wrapper transform generates a worker+-- that returns an unboxed tuple and yet internally does construct+-- the tuple, this suggests that using a custom return type should+-- help performance.+--+-- * TODO: the result of the inlined 'indexOfDifference' is still+-- being wrapped in ghc-prim:'GHC.Magic.lazy'; but nothing I can+-- do seems to change that. Is it something about the+-- 'unsafeDupablePerformIO' or what? Would changing it even help+-- performance?+--+-- * The first two cases can safely be allowed to fall through to+-- the @i <= 0@ case. After inlining, there shouldn't be any+-- function-call overhead for letting 'goByte' do the comparison+-- instead. The only difference is that the @i <= 0@ case will+-- hold onto @s0@/@s1@ rather than replacing them by 'S.empty'.+-- Unfortunately, that difference in liveness seems to result in+-- slightly worse performance.+-- TODO: a better benchmark than just running the test suite.+--+-- * The 'unsafeWithForeignPtr' allows for more aggressive optimization+-- than 'withForeignPtr', since it encodes the knowledge that the+-- continuation cannot diverge (loop, or throw exceptions). In+-- particular, without this, the call to 'min' will get hoisted+-- above the inner 'withForeignPtr' and the call to 'indexOfDifference'+-- will be duplicated in both branches of the 'min'; and since+-- 'indexOfDifference' will get inlined (recursive 'goBytes' and+-- all), that's a lot of code duplication. However, for whatever+-- reason the 'unsafeWithForeignPtr' version actually seems to+-- result in slightly worse performance (0.2~2% on the test suite).+-- TODO: a better benchmark than just running the test suite.+-- TODO: if that hoisting actually does help, then perhaps manually+-- lift the 'max' above both 'withForeignPtr' and manually+-- express the branch duplication.+-- TODO: Also consider whether this might be relevant:+-- <https://gitlab.haskell.org/ghc/ghc/-/issues/16556>+--+-- * TODO: should we yield to the accursed call of+-- 'Data.ByteString.Internal.accursedUnutterablePerformIO'?+-- Recent versions of bytestring export it, so we wouldn't+-- even need to copy the accursed incantation itself. Regarding+-- correctness, probably the closest thing to compare against+-- are these bugs against 'S.elemIndices':+-- <https://gitlab.haskell.org/ghc/ghc/-/issues/3487>+-- <https://gitlab.haskell.org/ghc/ghc/-/issues/3486>+--+-- * TODO: re-investigate performance of lifting the non-IO stuff+-- out of the scope of the 'unsafeDupablePerformIO', vs leaving+-- it within that scope.+--+breakMaximalPrefix+ s0@(PS fp0 off0 len0)+ s1@(PS fp1 off1 len1)+ | len0 <= 0 = strictTriple S.empty S.empty s1+ | len1 <= 0 = strictTriple S.empty s0 S.empty+ | otherwise =+ let i = unsafeDupablePerformIO $+ unsafeWithForeignPtr fp0 $ \p0 ->+ unsafeWithForeignPtr fp1 $ \p1 ->+ indexOfDifference+ (p0 `ptrElemOff` off0)+ (p1 `ptrElemOff` off1)+ (len0 `min` len1)+ in if i <= 0 -- can only be equal, but for paranoia's sake.+ then strictTriple S.empty s0 s1+ else strictTriple+ (if off0 + len0 < off1 + len1 -- share the smaller one+ then PS fp0 off0 i -- TODO: assert(i<=len0) for paranoia?+ else PS fp1 off1 i) -- TODO: assert(i<=len1) for paranoia?+ (dropPS i fp0 off0 len0)+ (dropPS i fp1 off1 len1)++-- | Construct a triple, strict in all arguments. This helps improve+-- code generation over our previous approach. Making our own+-- datatype for this result or CPSing 'breakMaximalPrefix' may still+-- improve things further.+strictTriple :: ByteString -> ByteString -> ByteString+ -> (ByteString, ByteString, ByteString)+strictTriple !p !s !z = (p,s,z)+{-# INLINE strictTriple #-}++-- | Get the 'sizeOf' type @a@, without requiring @-XScopedTypeVariables@+-- nor making a spurious call to 'System.IO.Unsafe.unsafePerformIO' or similar.+sizeOfElem :: Storable a => Ptr a -> Int+sizeOfElem = sizeOf . (undefined :: Ptr a -> a)+{-# INLINE sizeOfElem #-}++-- | C-style pointer addition, without the excessively liberal type+-- of 'plusPtr'.+ptrElemOff :: Storable a => Ptr a -> Int -> Ptr a+ptrElemOff p i = p `plusPtr` (i * sizeOfElem p)+{-# INLINE [0] ptrElemOff #-}+-- This rewrite rule helps ensure that on bytestring>=0.11 we don't+-- incur any additional cost for using the 'PS' pattern synonym.+{-# RULES+"Data.Trie.ByteStringInternal ptrElemOff/0"+ forall p . ptrElemOff p 0 = p+ #-}++-- For bytestring>=0.11, there's no way to improve over the 'PS'+-- constructor synonym here. After inlining, the @off=0@ from the+-- 'PS' pattern synonym will constant-propogate away, so all we'll+-- be left with is @BS (plusForeignPtr fp n) (len - n)@; which is+-- the same thing we would've written by hand. Plus, bytestring>=0.11+-- will already define the compatibility definition of 'plusForeignPtr'+-- for use with base<4.10.+--+-- | Unpacked version of 'S.drop', for use as a smart-constructor.+-- N.B., this assumes the @n <= 0@ case has already been handled+-- (otherwise you might as well just say @drop n (PS fp off len)@+-- and let the compiler remove the intermediate 'PS').+dropPS :: Int -> ForeignPtr ByteStringElem -> Int -> Int -> ByteString+dropPS !n !fp !off !len+ | n >= len = S.empty+ | otherwise = PS fp (off + n) (len - n)+{-# INLINE dropPS #-}+++------------------------------------------------------------+-- This naive algorithm doesn't depend on architecture details. We+-- could speed things up (in theory) by checking a natural word at+-- a time and then falling back to checking each byte once the+-- mismatched word is found. But in practice that doesn't seem to+-- actually speed things up.+--+-- TODO: that's probably because of alignment issues, or because+-- we should really vectorize by the largest single load on an+-- architecture rather than by the natural word size. For more+-- details on how to do it right, see GNU glibc's implementation+-- of @memcmp@. We should be able to do a simple twist on that+-- algorithm to return the index of difference rather than the+-- ordering. That would mean requiring GPL, but unfortunately every+-- other implementations of @memcmp@ I've found (FreeBSD libc, GCC's+-- builtin,...) just uses the same naive algorithm I have below.+-- I suppose we could always fork that algorithm off into a separate+-- optional dependency of this library; where we fallback to this+-- implementation if the user doesn't want the GPL burden.+--+-- | Calculates the first index where values differ.+indexOfDifference+ :: Ptr ByteStringElem+ -> Ptr ByteStringElem+ -> Int+ -> IO Int+indexOfDifference !p1 !p2 !limit = goByte 0+ where+ goByte n+ | n >= limit = return limit+ | otherwise = do+ c1 <- peekElemOff p1 n+ c2 <- peekElemOff p2 n+ if c1 == c2+ then goByte (n+1)+ else return n++-- TODO: why does bytestring-0.11 use 'peekByteOff' in lieu of+-- 'peekElemOff'? Given the definitions, the latter is more+-- direct/simpler: using @readWord8OffAddr# p# n# s@ instead of+-- @readWord8OffAddr# (plusAddr# p# n# ) 0# s@, though surely GHC+-- will optimize those to generate the same assembly.++------------------------------------------------------------+------------------------------------------------------------++-- | A \"reversed\" variant of lazy bytestrings; i.e., a snoc-list+-- of strict bytestrings.+data RevLazyByteString+ = RevLazyByteString :+> {-# UNPACK #-} !S.ByteString+ -- Invariant: every 'S.ByteString' is non-null.+ | Nil++-- TODO: should we add an 'assert' even though we don't check in general?+-- | \(\mathcal{O}(1)\). Unsafely\/uncheckedly append a BS to the+-- RLBS. It is up to the caller to maintain the invariant that+-- 'S.ByteString' is indeed non-null.+(+>!) :: RevLazyByteString -> S.ByteString -> RevLazyByteString+xs +>! x = xs :+> x+{-# INLINE (+>!) #-}++-- | \(\mathcal{O}(1)\). Safely append a BS to the RLBS, maintaining+-- the invariant.+(+>?) :: RevLazyByteString -> S.ByteString -> RevLazyByteString+xs +>? PS _ _ 0 = xs+xs +>? x = xs :+> x+{-# INLINE (+>?) #-}++-- | \(\mathcal{O}(1)\). Safely convert a strict BS to RLBS,+-- maintaining the invariant.+fromStrict :: S.ByteString -> RevLazyByteString+fromStrict = (Nil +>?)+{-# INLINE fromStrict #-}++-- HACK: bytestring-0.10.8.1 (GHC 8.0.2) used 'S.checkedSum' (and+-- a simpler algorithm), whereas bytestring-0.10.8.2 (GHC 8.2.1)+-- introduced 'S.checkedAdd' instead; alas, those version numbers+-- cannot be differentiated by the MIN_VERSION_bytestring macro.+-- Thus, we'll simply define it ourselves.+-- TODO: since we built the trie from bytestrings that were short+-- enough to have a valid length, do we actually need to perform+-- this check at all?+--+-- | Add two non-negative numbers. Errors out on overflow.+(+?) :: Int -> Int -> Int+x +? y+ | r >= 0 = r+ | otherwise = error overflowError+ where r = x + y+{-# INLINE (+?) #-}++overflowError :: String+overflowError = "Data.Trie.ByteStringInternal.toStrict: size overflow"+{-# NOINLINE overflowError #-}++-- See commentary at LazyByteString's version of @toStrict@. This+-- implementation is from Git SHA 688f3c0887f2ca0623f2f54f78e8f675f92e31bf,+-- modulo the necessary changes for using a snoc-list in lieu of a+-- cons-list.+-- | \(\mathcal{O}(n)\). Convert the RLBS to a strict BS, by copying it.+toStrict :: RevLazyByteString -> S.ByteString+toStrict = \cs0 -> goLen0 cs0 cs0+ where+ -- It's still possible that the result is empty.+ goLen0 _ Nil = S.empty+ goLen0 cs0 (cs :+> PS _ _ 0) = goLen0 cs0 cs+ goLen0 cs0 (cs :+> c) = goLen1 cs0 c cs+ -- It's still possible that the result is a single chunk.+ goLen1 _ b Nil = b+ goLen1 cs0 b (cs :+> PS _ _ 0) = goLen1 cs0 b cs+ goLen1 cs0 (PS _ _ bl) (cs :+> PS _ _ cl) = goLen cs0 (bl +? cl) cs+ -- General case, just find the total length we'll need.+ goLen cs0 !total (cs :+> PS _ _ cl) = goLen cs0 (total +? cl) cs+ goLen cs0 total Nil =+ S.unsafeCreate total $ \ptr ->+ -- TODO: this gives the correct behavior (re off-by-one+ -- concerns); however, it is bad praxis to use a pointer+ -- to something outside the allocated region; even if+ -- it is just pointing to the first invalid byte after+ -- the allocated region.+ goCopy cs0 (ptr `ptrElemOff` total)+ -- Copy the data+ goCopy Nil !_ = return ()+ goCopy (cs :+> PS _ _ 0 ) !ptr = goCopy cs ptr+ goCopy (cs :+> PS fp off len) !ptr =+ unsafeWithForeignPtr fp $ \p -> do+ let ptr' = ptr `ptrElemOff` negate len+ memcpy ptr' (p `ptrElemOff` off) len+ goCopy cs ptr'++------------------------------------------------------------+------------------------------------------------------- fin.
+ src/Data/Trie/Internal/Errors.hs view
@@ -0,0 +1,50 @@+{-# OPTIONS_GHC -Wall -fwarn-tabs #-}+{-# OPTIONS_HADDOCK hide #-}+{-# LANGUAGE CPP #-}+#if __GLASGOW_HASKELL__ >= 701+{-# LANGUAGE Safe #-}+#endif+----------------------------------------------------------------+-- ~ 2021.12.14+-- |+-- Module : Data.Trie.Internal.Errors+-- Copyright : 2008--2025 wren romano+-- License : BSD-3-Clause+-- Maintainer : wren@cpan.org+-- Stability : internal+-- Portability : portable+--+-- Internal convenience functions for giving error messages.+----------------------------------------------------------------++module Data.Trie.Internal.Errors (impossible) where++----------------------------------------------------------------+----------------------------------------------------------------++-- | The impossible happened. Use this instead of 'undefined'+-- whenever there's an unreachable case, an argument that shouldn't+-- ever get touched, etc.+impossible :: String -> a+impossible fn = error (formatMessage fn)+{-# INLINE impossible #-}+-- Inline the 'error' call itself, just not the string literals in the message.++formatMessage :: String -> String+formatMessage fn+ = "Data.Trie." ++ fn ++ ": The impossible happened."+ ++ "\nThis is a bug, please report it to the maintainer.\n"+{-# NOINLINE formatMessage #-}++-- N.B., at some point GHC adjusted 'error' to throw a+-- 'GHC.Exception.ErrorCall' which contains both the original message+-- and the location info. So we shouldn't have to resort to tricks+-- like *loch* or *placeholders* use in order to get the exact+-- location of the errors.+--+-- For older versions of GHC, see this post for how+-- 'Control.Exception.assert' gets turned into+-- 'GHC.IO.Exception.assertError': <https://stackoverflow.com/a/22997767>++----------------------------------------------------------------+----------------------------------------------------------- fin.