Skip to content

Commit 0b540ea

Browse files
Drop left-biased Semigroup/Monoid instances for Map; add SemigroupMap (#38)
* Added `SemigroupMap` newtype that has all instances of `Map`, plus an unbiased `Semigroup` and `Monoid` instance. * Added `Alt` and `Plus` instances for Data.Map's `Map` and `SemigroupMap` * Dropped `Semigroup` and `Monoid` instances for Data.Map's `Map`; the functionality provided by these instances can be recovered via `alt` and `empty` or by using `SemigroupMap`. * Relocated `asList` to be beneath the instances of `Map` * Added but did not export `foldSubmapBy` to make functions still work on `Map` now that it no longer has a `Semigroup` instance.
1 parent 02bf9ce commit 0b540ea

File tree

4 files changed

+104
-20
lines changed

4 files changed

+104
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ Notable changes to this project are documented in this file. The format is based
66

77
Breaking changes:
88
- Added support for PureScript 0.14 and dropped support for all previous versions (#35, #43)
9+
- Drop `Map`'s `Semigroup` and `Monoid` instances and provide unbiased instances via `SemigroupMap` newtype (#38)
910

1011
New features:
1112
- Added `Apply` instance for `Map` (#16)
13+
- Added `Alt` and `Plus` instances for `Map` (#38)
1214
- Added `catMaybes` for maps and sets (#25)
1315
- Added `toMap` and `fromMap` to `Data.Set` (#31)
1416

src/Data/Map.purs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,65 @@
11
module Data.Map
22
( module Data.Map.Internal
33
, keys
4+
, SemigroupMap(..)
45
) where
56

67
import Prelude
78

9+
import Control.Alt (class Alt)
10+
import Control.Plus (class Plus)
11+
import Data.Eq (class Eq1)
12+
import Data.Foldable (class Foldable)
13+
import Data.FoldableWithIndex (class FoldableWithIndex)
14+
import Data.FunctorWithIndex (class FunctorWithIndex)
815
import Data.Map.Internal (Map, alter, catMaybes, checkValid, delete, empty, filter, filterKeys, filterWithKey, findMax, findMin, foldSubmap, fromFoldable, fromFoldableWith, fromFoldableWithIndex, insert, insertWith, isEmpty, isSubmap, lookup, lookupGE, lookupGT, lookupLE, lookupLT, member, pop, showTree, singleton, size, submap, toUnfoldable, toUnfoldableUnordered, union, unionWith, unions, intersection, intersectionWith, difference, update, values, mapMaybeWithKey, mapMaybe)
16+
import Data.Newtype (class Newtype)
17+
import Data.Ord (class Ord1)
18+
import Data.Traversable (class Traversable)
19+
import Data.TraversableWithIndex (class TraversableWithIndex)
920
import Data.Set (Set, fromMap)
1021

1122
-- | The set of keys of the given map.
1223
-- | See also `Data.Set.fromMap`.
1324
keys :: forall k v. Map k v -> Set k
1425
keys = fromMap <<< void
26+
27+
-- | `SemigroupMap k v` provides a `Semigroup` instance for `Map k v` whose
28+
-- | definition depends on the `Semigroup` instance for the `v` type.
29+
-- | You should only use this type when you need `Data.Map` to have
30+
-- | a `Semigroup` instance.
31+
-- |
32+
-- | ```purescript
33+
-- | let
34+
-- | s :: forall key value. key -> value -> SemigroupMap key value
35+
-- | s k v = SemigroupMap (singleton k v)
36+
-- |
37+
-- | (s 1 "foo") <> (s 1 "bar") == (s 1 "foobar")
38+
-- | (s 1 (First 1)) <> (s 1 (First 2)) == (s 1 (First 1))
39+
-- | (s 1 (Last 1)) <> (s 1 (Last 2)) == (s 1 (Last 2))
40+
-- | ```
41+
newtype SemigroupMap k v = SemigroupMap (Map k v)
42+
43+
derive newtype instance eq1SemigroupMap :: Eq k => Eq1 (SemigroupMap k)
44+
derive newtype instance eqSemigroupMap :: (Eq k, Eq v) => Eq (SemigroupMap k v)
45+
derive newtype instance ord1SemigroupMap :: Ord k => Ord1 (SemigroupMap k)
46+
derive newtype instance ordSemigroupMap :: (Ord k, Ord v) => Ord (SemigroupMap k v)
47+
derive instance newtypeSemigroupMap :: Newtype (SemigroupMap k v) _
48+
derive newtype instance showSemigroupMap :: (Show k, Show v) => Show (SemigroupMap k v)
49+
50+
instance semigroupSemigroupMap :: (Ord k, Semigroup v) => Semigroup (SemigroupMap k v) where
51+
append (SemigroupMap l) (SemigroupMap r) = SemigroupMap (unionWith append l r)
52+
53+
instance monoidSemigroupMap :: (Ord k, Semigroup v) => Monoid (SemigroupMap k v) where
54+
mempty = SemigroupMap empty
55+
56+
derive newtype instance altSemigroupMap :: Ord k => Alt (SemigroupMap k)
57+
derive newtype instance plusSemigroupMap :: Ord k => Plus (SemigroupMap k)
58+
derive newtype instance functorSemigroupMap :: Functor (SemigroupMap k)
59+
derive newtype instance functorWithIndexSemigroupMap :: FunctorWithIndex k (SemigroupMap k)
60+
derive newtype instance applySemigroupMap :: Ord k => Apply (SemigroupMap k)
61+
derive newtype instance bindSemigroupMap :: Ord k => Bind (SemigroupMap k)
62+
derive newtype instance foldableSemigroupMap :: Foldable (SemigroupMap k)
63+
derive newtype instance foldableWithIndexSemigroupMap :: FoldableWithIndex k (SemigroupMap k)
64+
derive newtype instance traversableSemigroupMap :: Traversable (SemigroupMap k)
65+
derive newtype instance traversableWithIndexSemigroupMap :: TraversableWithIndex k (SemigroupMap k)

src/Data/Map/Internal.purs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ module Data.Map.Internal
4949

5050
import Prelude
5151

52+
import Control.Alt (class Alt)
53+
import Control.Plus (class Plus)
5254
import Data.Eq (class Eq1)
5355
import Data.Foldable (foldl, foldMap, foldr, class Foldable)
5456
import Data.FoldableWithIndex (class FoldableWithIndex, foldlWithIndex, foldrWithIndex)
@@ -90,11 +92,11 @@ instance ordMap :: (Ord k, Ord v) => Ord (Map k v) where
9092
instance showMap :: (Show k, Show v) => Show (Map k v) where
9193
show m = "(fromFoldable " <> show (toAscArray m) <> ")"
9294

93-
instance semigroupMap :: Ord k => Semigroup (Map k v) where
94-
append = union
95+
instance altMap :: Ord k => Alt (Map k) where
96+
alt = union
9597

96-
instance monoidMap :: Ord k => Monoid (Map k v) where
97-
mempty = empty
98+
instance plusMap :: Ord k => Plus (Map k) where
99+
empty = empty
98100

99101
instance functorMap :: Functor (Map k) where
100102
map _ Leaf = Leaf
@@ -122,9 +124,6 @@ instance foldableWithIndexMap :: FoldableWithIndex k (Map k) where
122124
foldrWithIndex f z m = foldr (uncurry f) z $ asList $ toUnfoldable m
123125
foldMapWithIndex f m = foldMap (uncurry f) $ asList $ toUnfoldable m
124126

125-
asList :: forall k v. List (Tuple k v) -> List (Tuple k v)
126-
asList = identity
127-
128127
instance traversableMap :: Traversable (Map k) where
129128
traverse f Leaf = pure Leaf
130129
traverse f (Two left k v right) =
@@ -158,6 +157,9 @@ instance traversableWithIndexMap :: TraversableWithIndex k (Map k) where
158157
<*> f k2 v2
159158
<*> traverseWithIndex f right
160159

160+
asList :: forall k v. List (Tuple k v) -> List (Tuple k v)
161+
asList = identity
162+
161163
-- | Render a `Map` as a `String`
162164
showTree :: forall k v. Show k => Show v => Map k v -> String
163165
showTree Leaf = "Leaf"
@@ -322,7 +324,10 @@ findMin = go Nothing
322324
-- | == ["zero", "one", "two"]
323325
-- | ```
324326
foldSubmap :: forall k v m. Ord k => Monoid m => Maybe k -> Maybe k -> (k -> v -> m) -> Map k v -> m
325-
foldSubmap kmin kmax f =
327+
foldSubmap = foldSubmapBy (<>) mempty
328+
329+
foldSubmapBy :: forall k v m. Ord k => (m -> m -> m) -> m -> Maybe k -> Maybe k -> (k -> v -> m) -> Map k v -> m
330+
foldSubmapBy appendFn memptyValue kmin kmax f =
326331
let
327332
tooSmall =
328333
case kmin of
@@ -367,17 +372,17 @@ foldSubmap kmin kmax f =
367372
-- function because of strictness.
368373
go = case _ of
369374
Leaf ->
370-
mempty
375+
memptyValue
371376
Two left k v right ->
372-
(if tooSmall k then mempty else go left)
373-
<> (if inBounds k then f k v else mempty)
374-
<> (if tooLarge k then mempty else go right)
377+
(if tooSmall k then memptyValue else go left)
378+
`appendFn` (if inBounds k then f k v else memptyValue)
379+
`appendFn` (if tooLarge k then memptyValue else go right)
375380
Three left k1 v1 mid k2 v2 right ->
376-
(if tooSmall k1 then mempty else go left)
377-
<> (if inBounds k1 then f k1 v1 else mempty)
378-
<> (if tooSmall k2 || tooLarge k1 then mempty else go mid)
379-
<> (if inBounds k2 then f k2 v2 else mempty)
380-
<> (if tooLarge k2 then mempty else go right)
381+
(if tooSmall k1 then memptyValue else go left)
382+
`appendFn` (if inBounds k1 then f k1 v1 else memptyValue)
383+
`appendFn` (if tooSmall k2 || tooLarge k1 then memptyValue else go mid)
384+
`appendFn` (if inBounds k2 then f k2 v2 else memptyValue)
385+
`appendFn` (if tooLarge k2 then memptyValue else go right)
381386
in
382387
go
383388

@@ -408,7 +413,7 @@ foldSubmap kmin kmax f =
408413
-- | else not (member key m')
409414
-- | ```
410415
submap :: forall k v. Ord k => Maybe k -> Maybe k -> Map k v -> Map k v
411-
submap kmin kmax = foldSubmap kmin kmax singleton
416+
submap kmin kmax = foldSubmapBy union empty kmin kmax singleton
412417

413418
-- | Test if a key is a member of a map
414419
member :: forall k v. Ord k => k -> Map k v -> Boolean

test/Test/Data/Map.purs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import Data.List.NonEmpty as NEL
1414
import Data.Map as M
1515
import Data.Map.Gen (genMap)
1616
import Data.Maybe (Maybe(..), fromMaybe, maybe)
17+
import Data.Semigroup.First (First(..))
18+
import Data.Semigroup.Last (Last(..))
1719
import Data.Tuple (Tuple(..), fst, uncurry)
1820
import Effect (Effect)
1921
import Effect.Console (log)
@@ -162,7 +164,7 @@ mapTests = do
162164

163165
log "sort . toUnfoldable . fromFoldable = sort (on lists without key-duplicates)"
164166
quickCheck $ \(list :: List (Tuple SmallKey Int)) ->
165-
let nubbedList = nubBy ((==) `on` fst) list
167+
let nubbedList = nubBy (compare `on` fst) list
166168
f x = M.toUnfoldable (M.fromFoldable x)
167169
in sort (f nubbedList) == sort nubbedList <?> show nubbedList
168170

@@ -254,7 +256,7 @@ mapTests = do
254256

255257
log "size"
256258
quickCheck $ \xs ->
257-
let xs' = nubBy ((==) `on` fst) xs
259+
let xs' = nubBy (compare `on` fst) xs
258260
in M.size (M.fromFoldable xs') == length (xs' :: List (Tuple SmallKey Int))
259261

260262
log "lookupLE result is correct"
@@ -399,3 +401,27 @@ mapTests = do
399401
let result = M.catMaybes maybeMap
400402
let expected = M.delete 1 m
401403
result === expected
404+
405+
log "SemigroupMap's Semigroup instance is based on value's Semigroup instance"
406+
quickCheck \(Tuple leftStr rightStr :: Tuple String String) -> do
407+
let key = "foo"
408+
let left = smSingleton key leftStr
409+
let right = smSingleton key rightStr
410+
let result = left <> right
411+
let expected = smSingleton key $ leftStr <> rightStr
412+
result == expected
413+
quickCheck \(Tuple leftStr rightStr :: Tuple String String) -> do
414+
let key = "foo"
415+
let left = smSingleton key $ First leftStr
416+
let right = smSingleton key $ First rightStr
417+
let result = left <> right
418+
result == left
419+
quickCheck \(Tuple leftStr rightStr :: Tuple String String) -> do
420+
let key = "foo"
421+
let left = smSingleton key $ Last leftStr
422+
let right = smSingleton key $ Last rightStr
423+
let result = left <> right
424+
result == right
425+
426+
smSingleton :: forall key value. key -> value -> M.SemigroupMap key value
427+
smSingleton k v = M.SemigroupMap (M.singleton k v)

0 commit comments

Comments
 (0)