monoidmap-examples 0.0.0.4 → 0.1.0.0
raw patch · 7 files changed
+1049/−3 lines, 7 filesdep +deepseqdep +hspec-quickcheck-classesdep +quickcheck-classesdep ~monoidmapPVP ok
version bump matches the API change (PVP)
Dependencies added: deepseq, hspec-quickcheck-classes, quickcheck-classes, tasty-bench, text
Dependency ranges changed: monoidmap
API changes (from Hackage documentation)
+ Data.MonoidMap.Examples.MultiSet: data MultiSet a
+ Data.MonoidMap.Examples.RecoveredMap: data Map k v
+ Data.MonoidMap.Examples.RecoveredMap: delete :: Ord k => k -> Map k v -> Map k v
+ Data.MonoidMap.Examples.RecoveredMap: empty :: Map k v
+ Data.MonoidMap.Examples.RecoveredMap: fromList :: Ord k => [(k, v)] -> Map k v
+ Data.MonoidMap.Examples.RecoveredMap: insert :: Ord k => k -> v -> Map k v -> Map k v
+ Data.MonoidMap.Examples.RecoveredMap: instance (Control.DeepSeq.NFData k, Control.DeepSeq.NFData v) => Control.DeepSeq.NFData (Data.MonoidMap.Examples.RecoveredMap.Map k v)
+ Data.MonoidMap.Examples.RecoveredMap: instance (GHC.Classes.Eq k, GHC.Classes.Eq v) => GHC.Classes.Eq (Data.MonoidMap.Examples.RecoveredMap.Map k v)
+ Data.MonoidMap.Examples.RecoveredMap: instance (GHC.Show.Show k, GHC.Show.Show v) => GHC.Show.Show (Data.MonoidMap.Examples.RecoveredMap.Map k v)
+ Data.MonoidMap.Examples.RecoveredMap: instance GHC.Base.Functor (Data.MonoidMap.Examples.RecoveredMap.Map k)
+ Data.MonoidMap.Examples.RecoveredMap: instance GHC.Classes.Ord k => GHC.Base.Monoid (Data.MonoidMap.Examples.RecoveredMap.Map k v)
+ Data.MonoidMap.Examples.RecoveredMap: instance GHC.Classes.Ord k => GHC.Base.Semigroup (Data.MonoidMap.Examples.RecoveredMap.Map k v)
+ Data.MonoidMap.Examples.RecoveredMap: keysSet :: Map k v -> Set k
+ Data.MonoidMap.Examples.RecoveredMap: lookup :: Ord k => k -> Map k v -> Maybe v
+ Data.MonoidMap.Examples.RecoveredMap: map :: (v1 -> v2) -> Map k v1 -> Map k v2
+ Data.MonoidMap.Examples.RecoveredMap: mapAccumL :: (s -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)
+ Data.MonoidMap.Examples.RecoveredMap: mapAccumLWithKey :: (s -> k -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)
+ Data.MonoidMap.Examples.RecoveredMap: mapAccumR :: (s -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)
+ Data.MonoidMap.Examples.RecoveredMap: mapAccumRWithKey :: (s -> k -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)
+ Data.MonoidMap.Examples.RecoveredMap: mapWithKey :: (k -> v1 -> v2) -> Map k v1 -> Map k v2
+ Data.MonoidMap.Examples.RecoveredMap: member :: Ord k => k -> Map k v -> Bool
+ Data.MonoidMap.Examples.RecoveredMap: singleton :: Ord k => k -> v -> Map k v
+ Data.MonoidMap.Examples.RecoveredMap: toList :: Map k v -> [(k, v)]
Files
- CHANGELOG.md +17/−0
- components/monoidmap-examples-benchmark/Main.hs +209/−0
- components/monoidmap-examples-test/Data/MonoidMap/Examples/RecoveredMapSpec.hs +584/−0
- components/monoidmap-examples-test/Test/Common.hs +67/−0
- components/monoidmap-examples/Data/MonoidMap/Examples/MultiSet.hs +2/−1
- components/monoidmap-examples/Data/MonoidMap/Examples/RecoveredMap.hs +130/−0
- monoidmap-examples.cabal +40/−2
CHANGELOG.md view
@@ -1,3 +1,20 @@+# 0.1.0.0++- Changed the role of the value type parameter of `NestedMonoidMap` from+ `representational` to `nominal`. This is a consequence of the same change+ to `MonoidMap` in `monoidmap-0.1.0.0`.++ This is a breaking change: code that coerces between `NestedMonoidMap`+ types with different value types will no longer compile.++- Added the `RecoveredMap` module, which provides a re-implementation+ of `Data.Map.Strict.Map` in terms of `MonoidMap`. (This module was+ previously defined within the `monoidmap-internal` package.)++- Exported the `MultiSet` type from the `MultiSet` module.++- Revised version bounds for dependencies.+ # 0.0.0.4 - Updated links to source repository in package metadata.
+ components/monoidmap-examples-benchmark/Main.hs view
@@ -0,0 +1,209 @@+-- |+-- Copyright: © 2022–2026 Jonathan Knowles+-- License: Apache-2.0+--+-- Benchmark for the 'MonoidMap' type and its functions.+--+-- When a 'MonoidMap' function has a direct counterpart in 'Map' (such as the+-- 'fromList' function), we benchmark 'Map' against 'RecoveredMap': a newtype+-- wrapper around 'MonoidMap' with functions that have the same semantics as+-- those of 'Map', so that both sides of each comparison behave identically.+--+module Main where++import Control.DeepSeq+ ( rnf )+import Control.Exception+ ( evaluate )+import Data.Eq+ ( Eq )+import Data.Function+ ( flip, ($) )+import Data.Int+ ( Int )+import Data.List+ ( filter, foldl', zip )+import Data.Maybe+ ( Maybe, fromMaybe )+import Data.Ord+ ( Ord )+import Data.Semigroup+ ( Semigroup ((<>)), stimes )+import Prelude+ ( Integer, Num, even, odd, (^), (+) )+import System.IO+ ( IO )+import Test.Tasty.Bench+ ( bench, bgroup, defaultMain, nf )++import qualified Data.Map.Strict as OMap+import qualified Data.MonoidMap.Examples.RecoveredMap as RMap++main :: IO ()+main = do++ let om_natural = fromList elems_natural :: OMap.Map Int Int+ om_even = fromList elems_even :: OMap.Map Int Int+ om_odd = fromList elems_odd :: OMap.Map Int Int++ rm_natural = fromList elems_natural :: RMap.Map Int Int+ rm_even = fromList elems_even :: RMap.Map Int Int+ rm_odd = fromList elems_odd :: RMap.Map Int Int++ evaluate $ rnf [om_natural, om_even, om_odd]+ evaluate $ rnf [rm_natural, rm_even, rm_odd]++ defaultMain+ [ bgroup "delete"+ [ bgroup "absent"+ [ bench "Data.Map.Strict" $+ nf (deleteMany evens) om_odd+ , bench "RecoveredMap" $+ nf (deleteMany evens) rm_odd+ ]+ , bgroup "present"+ [ bench "Data.Map.Strict" $+ nf (deleteMany evens) om_even+ , bench "RecoveredMap" $+ nf (deleteMany evens) rm_even+ ]+ ]+ , bgroup "insert"+ [ bgroup "absent"+ [ bench "Data.Map.Strict" $+ nf (insertMany elems_even) om_odd+ , bench "RecoveredMap" $+ nf (insertMany elems_even) rm_odd+ ]+ , bgroup "present"+ [ bench "Data.Map.Strict" $+ nf (insertMany elems_even) om_even+ , bench "RecoveredMap" $+ nf (insertMany elems_even) rm_even+ ]+ ]+ , bgroup "lookup"+ [ bgroup "absent"+ [ bench "Data.Map.Strict" $+ nf (lookupMany evens) om_odd+ , bench "RecoveredMap" $+ nf (lookupMany evens) rm_odd+ ]+ , bgroup "present"+ [ bench "Data.Map.Strict" $+ nf (lookupMany evens) om_even+ , bench "RecoveredMap" $+ nf (lookupMany evens) rm_even+ ]+ ]+ , bgroup "mappend"+ [ bgroup "disjoint"+ [ bench "Data.Map.Strict" $+ nf (<> om_even) om_odd+ , bench "RecoveredMap" $+ nf (<> rm_even) rm_odd+ ]+ , bgroup "identical"+ [ bench "Data.Map.Strict" $+ nf (<> om_even) om_even+ , bench "RecoveredMap" $+ nf (<> rm_even) rm_even+ ]+ ]+ , bgroup "stimes"+ [ bench "Data.Map.Strict" $+ nf (stimes ten_power_24) om_natural+ , bench "RecoveredMap" $+ nf (stimes ten_power_24) rm_natural+ ]+ , bgroup "mapAccumL"+ [ bench "Data.Map.Strict" $+ nf (mapAccumL (\s v -> (s + v, v)) 0) om_natural+ , bench "RecoveredMap" $+ nf (mapAccumL (\s v -> (s + v, v)) 0) rm_natural+ ]+ , bgroup "mapAccumR"+ [ bench "Data.Map.Strict" $+ nf (mapAccumR (\s v -> (s + v, v)) 0) om_natural+ , bench "RecoveredMap" $+ nf (mapAccumR (\s v -> (s + v, v)) 0) rm_natural+ ]+ , bgroup "mapAccumLWithKey"+ [ bench "Data.Map.Strict" $+ nf (mapAccumLWithKey (\s k v -> (s + k + v, v)) 0) om_natural+ , bench "RecoveredMap" $+ nf (mapAccumLWithKey (\s k v -> (s + k + v, v)) 0) rm_natural+ ]+ , bgroup "mapAccumRWithKey"+ [ bench "Data.Map.Strict" $+ nf (mapAccumRWithKey (\s k v -> (s + k + v, v)) 0) om_natural+ , bench "RecoveredMap" $+ nf (mapAccumRWithKey (\s k v -> (s + k + v, v)) 0) rm_natural+ ]+ ]+ where+ minNatural :: Int+ minNatural = 1++ maxNatural :: Int+ maxNatural = 2 ^ (16 :: Int)++ elems_natural :: [(Int, Int)]+ elems_natural = zip naturals naturals++ elems_even :: [(Int, Int)]+ elems_even = zip evens evens++ elems_odd :: [(Int, Int)]+ elems_odd = zip odds odds++ naturals :: [Int]+ naturals = [minNatural .. maxNatural]++ evens :: [Int]+ evens = filter even naturals++ odds :: [Int]+ odds = filter odd naturals++ ten_power_24 :: Integer+ ten_power_24 = 1_000_000_000_000_000_000_000_000++class Ord k => Map m k v where+ fromList :: [(k, v)] -> m k v+ delete :: k -> m k v -> m k v+ insert :: k -> v -> m k v -> m k v+ lookup :: k -> m k v -> Maybe v+ mapAccumL :: (s -> v -> (s, v)) -> s -> m k v -> (s, m k v)+ mapAccumR :: (s -> v -> (s, v)) -> s -> m k v -> (s, m k v)+ mapAccumLWithKey :: (s -> k -> v -> (s, v)) -> s -> m k v -> (s, m k v)+ mapAccumRWithKey :: (s -> k -> v -> (s, v)) -> s -> m k v -> (s, m k v)++instance Ord k => Map OMap.Map k v where+ fromList = OMap.fromList+ delete = OMap.delete+ insert = OMap.insert+ lookup = OMap.lookup+ mapAccumL = OMap.mapAccum+ mapAccumR f = OMap.mapAccumRWithKey (\s _ v -> f s v)+ mapAccumLWithKey = OMap.mapAccumWithKey+ mapAccumRWithKey = OMap.mapAccumRWithKey++instance (Ord k, Eq v) => Map RMap.Map k v where+ fromList = RMap.fromList+ delete = RMap.delete+ insert = RMap.insert+ lookup = RMap.lookup+ mapAccumL = RMap.mapAccumL+ mapAccumR = RMap.mapAccumR+ mapAccumLWithKey = RMap.mapAccumLWithKey+ mapAccumRWithKey = RMap.mapAccumRWithKey++deleteMany :: (Map m k v, Num v) => [k] -> m k v -> m k v+deleteMany xs m = foldl' (flip delete) m xs++insertMany :: (Map m k v, Num v) => [(k, v)] -> m k v -> m k v+insertMany xs m = foldl' (\m' (k, v) -> insert k v m') m xs++lookupMany :: (Map m k v, Num v) => [k] -> m k v -> v+lookupMany xs m = foldl' (\n k -> fromMaybe n (lookup k m)) 0 xs
+ components/monoidmap-examples-test/Data/MonoidMap/Examples/RecoveredMapSpec.hs view
@@ -0,0 +1,584 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}+{-# HLINT ignore "Use any" #-}+{-# HLINT ignore "Use null" #-}++-- |+-- Copyright: © 2022–2026 Jonathan Knowles+-- License: Apache-2.0+--+module Data.MonoidMap.Examples.RecoveredMapSpec+ where++import Prelude++import Data.Function+ ( on, (&) )+import Data.List+ ( nubBy )+import Data.Monoid+ ( Sum (..) )+import Data.Proxy+ ( Proxy (..) )+import Data.Semigroup+ ( Semigroup (stimes) )+import Data.Set+ ( Set )+import Data.Text+ ( Text )+import Data.Typeable+ ( Typeable, typeRep )+import Numeric.Natural+ ( Natural )+import Test.Common+ ()+import Test.Hspec+ ( Spec, describe, it )+import Test.QuickCheck+ ( Arbitrary (..)+ , CoArbitrary+ , Fun+ , Function+ , NonNegative (..)+ , Property+ , Testable+ , applyFun+ , applyFun2+ , applyFun3+ , checkCoverage+ , cover+ , listOf+ , shrinkMapBy+ , (===)+ )+import Test.QuickCheck.Classes+ ( eqLaws, functorLaws, monoidLaws, semigroupLaws, semigroupMonoidLaws )+import Test.Hspec.QuickCheck.Classes+ ( testLaws )++import qualified Data.Map.Strict as OMap+import qualified Data.Set as Set+import qualified Data.MonoidMap.Examples.RecoveredMap as RMap+import qualified Test.QuickCheck as QC++spec :: Spec+spec = do+ specFor (Proxy @Int) (Proxy @(Set Int))+ specFor (Proxy @Int) (Proxy @(Set Natural))+ specFor (Proxy @Int) (Proxy @(Sum Int))+ specFor (Proxy @Int) (Proxy @(Sum Natural))+ specFor (Proxy @Int) (Proxy @Text)++specFor+ :: forall k v. () =>+ ( Arbitrary k+ , Arbitrary v+ , CoArbitrary k+ , CoArbitrary v+ , Eq v+ , Function k+ , Function v+ , Monoid v+ , Ord k+ , Show k+ , Show v+ , Typeable k+ , Typeable v+ )+ => Proxy k+ -> Proxy v+ -> Spec+specFor keyType valueType = do++ let description = mconcat+ [ "RecoveredMap ("+ , show (typeRep keyType)+ , ") ("+ , show (typeRep valueType)+ , ")"+ ]++ let property :: Testable t => t -> Property+ property = checkCoverage . QC.property++ describe description $ do++ describe "Class laws" $ do+ testLaws @(RMap.Map k v)+ [ eqLaws+ , monoidLaws+ , semigroupLaws+ , semigroupMonoidLaws+ ]+ testLaws @(RMap.Map k)+ [ functorLaws+ ]++ describe "Conversion to and from lists" $ do+ it "prop_fromList_toList" $+ prop_fromList_toList+ @k @v & property++ describe "Empty" $ do+ it "prop_empty_keysSet" $+ prop_empty_keysSet+ @k & property+ it "prop_empty_lookup" $+ prop_empty_lookup+ @k @v & property+ it "prop_empty_show" $+ prop_empty_show+ @k @v & property+ it "prop_empty_toList" $+ prop_empty_toList+ @k @v & property++ describe "Singleton" $ do+ it "prop_singleton_keysSet" $+ prop_singleton_keysSet+ @k @v & property+ it "prop_singleton_lookup" $+ prop_singleton_lookup+ @k @v & property+ it "prop_singleton_show" $+ prop_singleton_show+ @k @v & property+ it "prop_singleton_toList" $+ prop_singleton_toList+ @k @v & property++ describe "Append" $ do+ it "prop_append_toList" $+ prop_append_toList+ @k @v & property++ describe "Times" $ do+ it "prop_stimes_toList" $+ prop_stimes_toList+ @k @v & property++ describe "Delete" $ do+ it "prop_delete_lookup" $+ prop_delete_lookup+ @k @v & property+ it "prop_delete_member" $+ prop_delete_member+ @k @v & property+ it "prop_delete_toList" $+ prop_delete_toList+ @k @v & property++ describe "Insert" $ do+ it "prop_insert_lookup" $+ prop_insert_lookup+ @k @v & property+ it "prop_insert_member" $+ prop_insert_member+ @k @v & property+ it "prop_insert_toList" $+ prop_insert_toList+ @k @v & property++ describe "Map" $ do+ it "prop_map" $+ prop_map+ @k @v @v & property+ it "prop_map_mempty" $+ prop_map_mempty+ @k @v @v & property+ it "prop_mapWithKey" $+ prop_mapWithKey+ @k @v @v & property++ describe "MapAccumL" $ do+ it "prop_mapAccumL @Int" $+ prop_mapAccumL @Int+ @k @v @v & property+ it "prop_mapAccumL @String" $+ prop_mapAccumL @String+ @k @v @v & property++ describe "MapAccumR" $ do+ it "prop_mapAccumR @Int" $+ prop_mapAccumR @Int+ @k @v @v & property+ it "prop_mapAccumR @String" $+ prop_mapAccumR @String+ @k @v @v & property++ describe "MapAccumWithKeyL" $ do+ it "prop_mapAccumLWithKey @Int" $+ prop_mapAccumLWithKey @Int+ @k @v @v & property+ it "prop_mapAccumLWithKey @String" $+ prop_mapAccumLWithKey @String+ @k @v @v & property++ describe "MapAccumWithKeyR" $ do+ it "prop_mapAccumRWithKey @Int" $+ prop_mapAccumRWithKey @Int+ @k @v @v & property+ it "prop_mapAccumRWithKey @String" $+ prop_mapAccumRWithKey @String+ @k @v @v & property++--------------------------------------------------------------------------------+-- Conversion to and from lists+--------------------------------------------------------------------------------++prop_fromList_toList+ :: forall k v. (Ord k, Show k, Eq v, Show v)+ => [(k, v)]+ -> Property+prop_fromList_toList kvs =+ (===)+ (RMap.toList (RMap.fromList kvs))+ (OMap.toList (OMap.fromList kvs))+ & cover 10+ (length kvs > 1 && length (nubBy ((==) `on` fst) kvs) /= length kvs)+ "length kvs > 1 && length (nubBy ((==) `on` fst) kvs) /= length kvs"+ & cover 10+ (length kvs > 1 && length (nubBy ((==) `on` fst) kvs) == length kvs)+ "length kvs > 1 && length (nubBy ((==) `on` fst) kvs) == length kvs"++--------------------------------------------------------------------------------+-- Empty+--------------------------------------------------------------------------------++prop_empty_keysSet+ :: forall k. (Eq k, Show k)+ => Property+prop_empty_keysSet =+ (===)+ (RMap.keysSet (RMap.empty @k))+ (OMap.keysSet (OMap.empty @k))++prop_empty_lookup+ :: forall k v. (Ord k, Eq v, Show v)+ => k+ -> Property+prop_empty_lookup k =+ (===)+ (RMap.lookup k (RMap.empty @k @v))+ (OMap.lookup k (OMap.empty @k @v))++prop_empty_show+ :: forall k v. (Show k, Show v)+ => Property+prop_empty_show =+ (===)+ (show (RMap.empty @k @v))+ (show (OMap.empty @k @v))++prop_empty_toList+ :: forall k v. (Eq k, Show k, Eq v, Show v)+ => Property+prop_empty_toList =+ (===)+ (RMap.toList (RMap.empty @k @v))+ (OMap.toList (OMap.empty @k @v))++--------------------------------------------------------------------------------+-- Singleton+--------------------------------------------------------------------------------++prop_singleton_keysSet+ :: forall k v. (Ord k, Show k)+ => k+ -> v+ -> Property+prop_singleton_keysSet k v =+ (===)+ (RMap.keysSet (RMap.singleton k v))+ (OMap.keysSet (OMap.singleton k v))++prop_singleton_lookup+ :: forall k v. (Ord k, Eq v, Show v)+ => k+ -> v+ -> Property+prop_singleton_lookup k v =+ (===)+ (RMap.lookup k (RMap.singleton k v))+ (OMap.lookup k (OMap.singleton k v))++prop_singleton_show+ :: forall k v. (Ord k, Show k, Show v)+ => k+ -> v+ -> Property+prop_singleton_show k v =+ (===)+ (show (RMap.singleton k v))+ (show (OMap.singleton k v))++prop_singleton_toList+ :: forall k v. (Ord k, Show k, Eq v, Show v)+ => k+ -> v+ -> Property+prop_singleton_toList k v =+ (===)+ (RMap.toList (RMap.singleton k v))+ (OMap.toList (OMap.singleton k v))++--------------------------------------------------------------------------------+-- Append+--------------------------------------------------------------------------------++prop_append_toList+ :: forall k v. (Ord k, Show k, Eq v, Show v)+ => [(k, v)]+ -> [(k, v)]+ -> Property+prop_append_toList kvs1 kvs2 =+ (===)+ (RMap.toList (RMap.fromList kvs1 <> RMap.fromList kvs2))+ (OMap.toList (OMap.fromList kvs1 <> OMap.fromList kvs2))+ & cover 10+ (ks1 `Set.disjoint` ks2)+ "ks1 `Set.disjoint` ks2"+ & cover 10+ (not (ks1 `Set.disjoint` ks2))+ "not (ks1 `Set.disjoint` ks2)"+ where+ ks1 = Set.fromList (fst <$> kvs1)+ ks2 = Set.fromList (fst <$> kvs2)++--------------------------------------------------------------------------------+-- Times+--------------------------------------------------------------------------------++prop_stimes_toList+ :: forall k v. (Ord k, Show k, Eq v, Show v)+ => [(k, v)]+ -> NonNegative Int+ -> Property+prop_stimes_toList kvs (NonNegative n) =+ (===)+ (RMap.toList (stimes n (RMap.fromList kvs)))+ (OMap.toList (stimes n (OMap.fromList kvs)))+ & cover 1+ (n == 0)+ "n == 0"+ & cover 1+ (n == 1)+ "n == 1"+ & cover 10+ (n >= 2)+ "n >= 2"++--------------------------------------------------------------------------------+-- Delete+--------------------------------------------------------------------------------++prop_delete_lookup+ :: forall k v. (Ord k, Eq v, Show v)+ => [(k, v)]+ -> k+ -> Property+prop_delete_lookup kvs k =+ (===)+ (RMap.lookup k (RMap.delete k (RMap.fromList kvs)))+ (OMap.lookup k (OMap.delete k (OMap.fromList kvs)))+ & cover 10+ (filter ((== k) . fst) kvs == [])+ "filter ((== k) . fst) kvs == []"+ & cover 10+ (filter ((== k) . fst) kvs /= [])+ "filter ((== k) . fst) kvs /= []"++prop_delete_member+ :: forall k v. (Ord k, Eq v)+ => [(k, v)]+ -> k+ -> Property+prop_delete_member kvs k =+ (===)+ (RMap.member k (RMap.delete k (RMap.fromList kvs)))+ (OMap.member k (OMap.delete k (OMap.fromList kvs)))+ & cover 10+ (filter ((== k) . fst) kvs == [])+ "filter ((== k) . fst) kvs == []"+ & cover 10+ (filter ((== k) . fst) kvs /= [])+ "filter ((== k) . fst) kvs /= []"++prop_delete_toList+ :: forall k v. (Ord k, Show k, Eq v, Show v)+ => [(k, v)]+ -> k+ -> Property+prop_delete_toList kvs k =+ (===)+ (RMap.toList (RMap.delete k (RMap.fromList kvs)))+ (OMap.toList (OMap.delete k (OMap.fromList kvs)))+ & cover 10+ (filter ((== k) . fst) kvs == [])+ "filter ((== k) . fst) kvs == []"+ & cover 10+ (filter ((== k) . fst) kvs /= [])+ "filter ((== k) . fst) kvs /= []"++--------------------------------------------------------------------------------+-- Insert+--------------------------------------------------------------------------------++prop_insert_lookup+ :: forall k v. (Ord k, Eq v, Show v)+ => [(k, v)]+ -> k+ -> v+ -> Property+prop_insert_lookup kvs k v =+ (===)+ (RMap.lookup k (RMap.insert k v (RMap.fromList kvs)))+ (OMap.lookup k (OMap.insert k v (OMap.fromList kvs)))+ & cover 10+ (filter ((== k) . fst) kvs == [])+ "filter ((== k) . fst) kvs == []"+ & cover 10+ (filter ((== k) . fst) kvs /= [])+ "filter ((== k) . fst) kvs /= []"++prop_insert_member+ :: forall k v. (Ord k, Eq v)+ => [(k, v)]+ -> k+ -> v+ -> Property+prop_insert_member kvs k v =+ (===)+ (RMap.member k (RMap.insert k v (RMap.fromList kvs)))+ (OMap.member k (OMap.insert k v (OMap.fromList kvs)))+ & cover 10+ (filter ((== k) . fst) kvs == [])+ "filter ((== k) . fst) kvs == []"+ & cover 10+ (filter ((== k) . fst) kvs /= [])+ "filter ((== k) . fst) kvs /= []"++prop_insert_toList+ :: forall k v. (Ord k, Show k, Eq v, Show v)+ => [(k, v)]+ -> k+ -> v+ -> Property+prop_insert_toList kvs k v =+ (===)+ (RMap.toList (RMap.insert k v (RMap.fromList kvs)))+ (OMap.toList (OMap.insert k v (OMap.fromList kvs)))+ & cover 10+ (filter ((== k) . fst) kvs == [])+ "filter ((== k) . fst) kvs == []"+ & cover 10+ (filter ((== k) . fst) kvs /= [])+ "filter ((== k) . fst) kvs /= []"++--------------------------------------------------------------------------------+-- Map+--------------------------------------------------------------------------------++prop_map+ :: (Ord k, Show k, Eq v2, Show v2)+ => [(k, v1)]+ -> Fun v1 v2+ -> Property+prop_map kvs (applyFun -> f) =+ (===)+ (RMap.toList (RMap.map f (RMap.fromList kvs)))+ (OMap.toList (OMap.map f (OMap.fromList kvs)))++prop_map_mempty+ :: forall k v1 v2. (Ord k, Show k, Eq v2, Monoid v2, Show v2)+ => [(k, v1)]+ -> Property+prop_map_mempty kvs =+ (===)+ (RMap.toList (RMap.map (const (mempty @v2)) (RMap.fromList kvs)))+ (OMap.toList (OMap.map (const (mempty @v2)) (OMap.fromList kvs)))++prop_mapWithKey+ :: (Ord k, Show k, Eq v2, Show v2)+ => [(k, v1)]+ -> Fun (k, v1) v2+ -> Property+prop_mapWithKey kvs (applyFun2 -> f) =+ (===)+ (RMap.toList (RMap.mapWithKey f (RMap.fromList kvs)))+ (OMap.toList (OMap.mapWithKey f (OMap.fromList kvs)))++--------------------------------------------------------------------------------+-- MapAccum+--------------------------------------------------------------------------------++prop_mapAccumL+ :: forall s k v1 v2. (Eq s, Eq v2, Ord k, Show k, Show s, Show v2)+ => Fun (s, v1) (s, v2)+ -> s+ -> [(k, v1)]+ -> Property+prop_mapAccumL (applyFun2 -> f) s0 kvs =+ (===)+ (RMap.toList <$> rmapAccumL f s0 (RMap.fromList kvs))+ (OMap.toList <$> omapAccumL f s0 (OMap.fromList kvs))+ where+ rmapAccumL = RMap.mapAccumL+ omapAccumL = OMap.mapAccum++prop_mapAccumR+ :: forall s k v1 v2. (Eq s, Eq v2, Ord k, Show k, Show s, Show v2)+ => Fun (s, v1) (s, v2)+ -> s+ -> [(k, v1)]+ -> Property+prop_mapAccumR (applyFun2 -> f) s0 kvs =+ (===)+ (RMap.toList <$> rmapAccumR f s0 (RMap.fromList kvs))+ (OMap.toList <$> omapAccumR f s0 (OMap.fromList kvs))+ where+ rmapAccumR = RMap.mapAccumR+ omapAccumR g = OMap.mapAccumRWithKey (\s _ v -> g s v)++--------------------------------------------------------------------------------+-- MapAccumWithKey+--------------------------------------------------------------------------------++prop_mapAccumLWithKey+ :: forall s k v1 v2. (Eq s, Eq v2, Ord k, Show k, Show s, Show v2)+ => Fun (s, k, v1) (s, v2)+ -> s+ -> [(k, v1)]+ -> Property+prop_mapAccumLWithKey (applyFun3 -> f) s0 kvs =+ (===)+ (RMap.toList <$> rmapAccumLWithKey f s0 (RMap.fromList kvs))+ (OMap.toList <$> omapAccumLWithKey f s0 (OMap.fromList kvs))+ where+ rmapAccumLWithKey = RMap.mapAccumLWithKey+ omapAccumLWithKey = OMap.mapAccumWithKey++prop_mapAccumRWithKey+ :: forall s k v1 v2. (Eq s, Eq v2, Ord k, Show k, Show s, Show v2)+ => Fun (s, k, v1) (s, v2)+ -> s+ -> [(k, v1)]+ -> Property+prop_mapAccumRWithKey (applyFun3 -> f) s0 kvs =+ (===)+ (RMap.toList <$> rmapAccumRWithKey f s0 (RMap.fromList kvs))+ (OMap.toList <$> omapAccumRWithKey f s0 (OMap.fromList kvs))+ where+ rmapAccumRWithKey = RMap.mapAccumRWithKey+ omapAccumRWithKey = OMap.mapAccumRWithKey++--------------------------------------------------------------------------------+-- Arbitrary instances+--------------------------------------------------------------------------------++instance (Arbitrary k, Ord k, Arbitrary v) =>+ Arbitrary (RMap.Map k v)+ where+ arbitrary = RMap.fromList <$> listOf ((,) <$> arbitrary <*> arbitrary)+ shrink = shrinkMapBy RMap.fromList RMap.toList shrink
+ components/monoidmap-examples-test/Test/Common.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- |+-- Copyright: © 2022–2026 Jonathan Knowles+-- License: Apache-2.0+--+module Test.Common+ () where++import Prelude++import Data.Text+ ( Text )+import Numeric.Natural+ ( Natural )+import Test.QuickCheck+ ( Arbitrary (..)+ , CoArbitrary (..)+ , Function (..)+ , arbitrarySizedNatural+ , coarbitraryIntegral+ , coarbitraryShow+ , frequency+ , functionIntegral+ , functionShow+ , listOf+ , shrinkIntegral+ )++import qualified Data.Text as Text++--------------------------------------------------------------------------------+-- Arbitrary instances+--------------------------------------------------------------------------------++#if !MIN_VERSION_QuickCheck(2,17,0)+instance Arbitrary Natural where+ arbitrary = arbitrarySizedNatural+ shrink = shrinkIntegral+#endif++#if !MIN_VERSION_QuickCheck(2,18,0)+instance CoArbitrary Natural where+ coarbitrary = coarbitraryIntegral+#endif++#if !MIN_VERSION_QuickCheck(2,18,0)+instance Function Natural where+ function = functionIntegral+#endif++instance Arbitrary Text where+ arbitrary = Text.pack <$> listOf genChar+ where+ genChar = frequency+ [ (64, pure 'a')+ , (16, pure 'b')+ , ( 4, pure 'c')+ , ( 1, pure 'd')+ ]++instance CoArbitrary Text where+ coarbitrary = coarbitraryShow++instance Function Text where+ function = functionShow
components/monoidmap-examples/Data/MonoidMap/Examples/MultiSet.hs view
@@ -7,7 +7,8 @@ -- See: https://en.wikipedia.org/wiki/Multiset -- module Data.MonoidMap.Examples.MultiSet- ( fromList+ ( MultiSet+ , fromList , toList , null , member
+ components/monoidmap-examples/Data/MonoidMap/Examples/RecoveredMap.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- |+-- Copyright: © 2022–2026 Jonathan Knowles+-- License: Apache-2.0+--+-- An ordinary left-biased map similar to 'Data.Map.Strict.Map', implemented in+-- terms of 'MonoidMap'.+--+-- Since 'MonoidMap' is itself implemented in terms of 'Data.Map.Strict.Map',+-- this module demonstrates that 'MonoidMap' and 'Data.Map.Strict.Map' are+-- equally general: each can be used to implement the other.+--+module Data.MonoidMap.Examples.RecoveredMap+ ( Map+ , empty+ , singleton+ , fromList+ , toList+ , delete+ , insert+ , keysSet+ , lookup+ , member+ , map+ , mapWithKey+ , mapAccumL+ , mapAccumLWithKey+ , mapAccumR+ , mapAccumRWithKey+ )+ where++import Prelude hiding+ ( lookup, map )++import Control.DeepSeq+ ( NFData )+import Data.Coerce+ ( coerce )+import Data.Maybe+ ( mapMaybe )+import Data.Monoid+ ( First (..) )+import Data.MonoidMap+ ( MonoidMap )+import Data.Semigroup+ ( Semigroup (stimes), stimesIdempotentMonoid )+import Data.Set+ ( Set )++import qualified Data.MonoidMap as MonoidMap++newtype Map k v = Map+ -- 'First' is used to mimic the left-biased nature of+ -- 'Data.Map.Strict.Map':+ {unMap :: MonoidMap k (First v)}+ deriving newtype (Eq, NFData, Monoid)++instance Ord k => Semigroup (Map k v) where+ (<>) = coerce @(MonoidMap k (First v) -> _ -> _) (<>)+ stimes = stimesIdempotentMonoid++instance (Show k, Show v) => Show (Map k v) where+ show = ("fromList " <>) . show . toList++instance Functor (Map k) where+ fmap = map++empty :: Map k v+empty = Map MonoidMap.empty++singleton :: Ord k => k -> v -> Map k v+singleton k = Map . MonoidMap.singleton k . pure++fromList :: Ord k => [(k, v)] -> Map k v+fromList = Map . MonoidMap.fromListWith (const id) . fmap (fmap pure)++toList :: Map k v -> [(k, v)]+toList = mapMaybe (getFirst . sequenceA) . MonoidMap.toList . unMap++delete :: Ord k => k -> Map k v -> Map k v+delete k = Map . MonoidMap.nullify k . unMap++insert :: Ord k => k -> v -> Map k v -> Map k v+insert k v = Map . MonoidMap.set k (pure v) . unMap++keysSet :: Map k v -> Set k+keysSet = MonoidMap.nonNullKeys . unMap++lookup :: Ord k => k -> Map k v -> Maybe v+lookup k = getFirst . MonoidMap.get k . unMap++member :: Ord k => k -> Map k v -> Bool+member k = MonoidMap.nonNullKey k . unMap++map :: (v1 -> v2) -> Map k v1 -> Map k v2+map f = Map . MonoidMap.map (fmap f) . unMap++mapWithKey :: (k -> v1 -> v2) -> Map k v1 -> Map k v2+mapWithKey f = Map . MonoidMap.mapWithKey (fmap . f) . unMap++mapAccumL :: (s -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)+mapAccumL f s m = Map <$> MonoidMap.mapAccumL (accum f) s (unMap m)++mapAccumR :: (s -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)+mapAccumR f s m = Map <$> MonoidMap.mapAccumR (accum f) s (unMap m)++mapAccumLWithKey :: (s -> k -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)+mapAccumLWithKey f s m =+ Map <$> MonoidMap.mapAccumLWithKey (accumWithKey f) s (unMap m)++mapAccumRWithKey :: (s -> k -> v1 -> (s, v2)) -> s -> Map k v1 -> (s, Map k v2)+mapAccumRWithKey f s m =+ Map <$> MonoidMap.mapAccumRWithKey (accumWithKey f) s (unMap m)++--------------------------------------------------------------------------------+-- Utilities+--------------------------------------------------------------------------------++accum :: (s -> v1 -> (s, v2)) -> s -> First v1 -> (s, First v2)+accum f s1 (First mv1) = case mv1 of+ Just v1 -> let (s2, v2) = f s1 v1 in (s2, First (Just v2))+ Nothing -> (s1, First Nothing)++accumWithKey :: (s -> k -> v1 -> (s, v2)) -> s -> k -> First v1 -> (s, First v2)+accumWithKey f s1 k (First mv1) = case mv1 of+ Just v1 -> let (s2, v2) = f s1 k v1 in (s2, First (Just v2))+ Nothing -> (s1, First Nothing)
monoidmap-examples.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: monoidmap-examples-version: 0.0.0.4+version: 0.1.0.0 homepage: https://github.com/jonathanknowles/monoidmap/tree/HEAD/packages/monoidmap-examples bug-reports: https://github.com/jonathanknowles/monoidmap/issues license: Apache-2.0@@ -20,14 +20,24 @@ build-depends:base >= 4.14.3.0 && < 4.23 common dependency-containers build-depends:containers >= 0.6.5.1 && < 0.9+common dependency-deepseq+ build-depends:deepseq >= 1.4.4.0 && < 1.6 common dependency-hspec build-depends:hspec >= 2.10.9 && < 2.12+common dependency-hspec-quickcheck-classes+ build-depends:hspec-quickcheck-classes >= 0.0.0.0 && < 0.1 common dependency-monoid-subclasses build-depends:monoid-subclasses >= 1.2.3 && < 1.3 common dependency-monoidmap- build-depends:monoidmap >= 0.0.4.4 && < 0.1+ build-depends:monoidmap >= 0.1.0.0 && < 0.2 common dependency-QuickCheck build-depends:QuickCheck >= 2.14.2 && < 2.20+common dependency-quickcheck-classes+ build-depends:quickcheck-classes >= 0.6.5.0 && < 0.7+common dependency-tasty-bench+ build-depends:tasty-bench >= 0.3.2 && < 0.6+common dependency-text+ build-depends:text >= 1.2.4.1 && < 2.2 common extensions default-extensions:@@ -58,6 +68,7 @@ import: , dependency-base , dependency-containers+ , dependency-deepseq , dependency-monoid-subclasses , dependency-monoidmap , extensions@@ -72,16 +83,41 @@ Data.MonoidMap.Examples.MultiMap.Instances.MultiMap4 Data.MonoidMap.Examples.MultiSet Data.MonoidMap.Examples.NestedMonoidMap+ Data.MonoidMap.Examples.RecoveredMap Data.MonoidMap.Examples.Set.NonEmpty default-language: Haskell2010 +benchmark monoidmap-examples-benchmark+ import:+ , dependency-base+ , dependency-containers+ , dependency-deepseq+ , dependency-tasty-bench+ , extensions+ build-depends:+ , monoidmap-examples+ default-language:+ Haskell2010+ type:+ exitcode-stdio-1.0+ hs-source-dirs:+ components/monoidmap-examples-benchmark+ main-is:+ Main.hs+ -- Report allocation and memory statistics for each benchmark:+ ghc-options:+ "-with-rtsopts=-T"+ test-suite monoidmap-examples-test import: , dependency-base , dependency-containers , dependency-hspec+ , dependency-hspec-quickcheck-classes , dependency-QuickCheck+ , dependency-quickcheck-classes+ , dependency-text , extensions build-depends: , monoidmap-examples@@ -93,7 +129,9 @@ components/monoidmap-examples-test other-modules: Data.MonoidMap.Examples.MultiMapSpec+ Data.MonoidMap.Examples.RecoveredMapSpec SpecHook+ Test.Common type: exitcode-stdio-1.0 default-language: