Skip to content
This repository was archived by the owner on Oct 4, 2020. It is now read-only.

Commit 7213be8

Browse files
committed
Improvements for StrMap: optimizations, add unsafeIndex, size, foldM, foldMap, Semigroup instance
Since we know that all StrMap objects have prototype Object, we know they have no additional enumerable keys, so hasOwnProperty checks are unnecessary. Add efficient JS implementations for keys and values. Add standard Monoid and Monad fold functions, and use them internally. Minor other changes to eliminate unnecessary function layers and variables. Fix and update tests.
1 parent 3fc0d05 commit 7213be8

File tree

4 files changed

+136
-68
lines changed

4 files changed

+136
-68
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,15 @@
142142

143143
instance functorStrMap :: P.Functor StrMap
144144

145+
instance semigroupStrMap :: (P.Semigroup a) => P.Semigroup (StrMap a)
146+
145147
instance showStrMap :: (P.Show a) => P.Show (StrMap a)
146148

147149

148150
### Values
149151

152+
all :: forall a. (String -> a -> Boolean) -> StrMap a -> Boolean
153+
150154
alter :: forall a. (Maybe a -> Maybe a) -> String -> StrMap a -> StrMap a
151155

152156
delete :: forall a. String -> StrMap a -> StrMap a
@@ -155,6 +159,10 @@
155159

156160
fold :: forall a z. (z -> String -> a -> z) -> z -> StrMap a -> z
157161

162+
foldM :: forall a m z. (P.Monad m) => (z -> String -> a -> m z) -> z -> StrMap a -> m z
163+
164+
foldMap :: forall a m. (Monoid m) => (String -> a -> m) -> StrMap a -> m
165+
158166
foldMaybe :: forall a z. (z -> String -> a -> Maybe z) -> z -> StrMap a -> z
159167

160168
fromList :: forall a. [Tuple String a] -> StrMap a
@@ -175,6 +183,8 @@
175183

176184
singleton :: forall a. String -> a -> StrMap a
177185

186+
size :: forall a. StrMap a -> Number
187+
178188
toList :: forall a. StrMap a -> [Tuple String a]
179189

180190
union :: forall a. StrMap a -> StrMap a -> StrMap a
@@ -183,4 +193,11 @@
183193

184194
update :: forall a. (a -> Maybe a) -> String -> StrMap a -> StrMap a
185195

186-
values :: forall a. StrMap a -> [a]
196+
values :: forall a. StrMap a -> [a]
197+
198+
199+
## Module Data.StrMap.Unsafe
200+
201+
### Values
202+
203+
unsafeIndex :: forall a. StrMap a -> String -> a

src/Data/StrMap.purs

Lines changed: 92 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module Data.StrMap
88
( StrMap(),
99
empty,
1010
isEmpty,
11+
size,
1112
singleton,
1213
insert,
1314
lookup,
@@ -24,7 +25,10 @@ module Data.StrMap
2425
map,
2526
isSubmap,
2627
fold,
27-
foldMaybe
28+
foldMap,
29+
foldM,
30+
foldMaybe,
31+
all
2832
) where
2933

3034
import qualified Prelude as P
@@ -34,50 +38,77 @@ import Data.Maybe
3438
import Data.Function
3539
import Data.Tuple
3640
import Data.Foldable (foldl)
41+
import Data.Monoid
42+
import Data.Monoid.All
3743

3844
foreign import data StrMap :: * -> *
3945

40-
foreign import _foldStrMap
41-
"function _foldStrMap(m, z0, f) {\
42-
\ var z = z0;\
43-
\ for (var k in m) {\
44-
\ if (m.hasOwnProperty(k)) z = f(z)(k)(m[k]);\
45-
\ }\
46-
\ return z;\
47-
\}" :: forall v z. Fn3 (StrMap v) z (z -> String -> v -> z) z
48-
49-
fold :: forall a z. (z -> String -> a -> z) -> z -> (StrMap a) -> z
50-
fold f z m = runFn3 _foldStrMap m z f
51-
5246
foreign import _fmapStrMap
5347
"function _fmapStrMap(m0, f) {\
5448
\ var m = {};\
5549
\ for (var k in m0) {\
56-
\ if (m0.hasOwnProperty(k)) m[k] = f(m0[k]);\
50+
\ m[k] = f(m0[k]);\
5751
\ }\
5852
\ return m;\
5953
\}" :: forall a b. Fn2 (StrMap a) (a -> b) (StrMap b)
6054

6155
instance functorStrMap :: P.Functor StrMap where
6256
(<$>) f m = runFn2 _fmapStrMap m f
6357

58+
-- It would be nice to have a Foldable instance, but we're essentially unordered
59+
60+
foreign import _foldM
61+
"function _foldM(bind) {\
62+
\ return function(f) {\
63+
\ return function (mz) {\
64+
\ return function (m) {\
65+
\ var k;\
66+
\ function g(z) {\
67+
\ return f(z)(k)(m[k]);\
68+
\ }\
69+
\ for (k in m)\
70+
\ mz = bind(mz)(g);\
71+
\ return mz;\
72+
\ };\
73+
\ };\
74+
\ };\
75+
\}" :: forall a m z. (m -> (z -> m) -> m) -> (z -> String -> a -> m) -> m -> StrMap a -> m
76+
77+
fold :: forall a z. (z -> String -> a -> z) -> z -> StrMap a -> z
78+
fold = _foldM (P.(#))
79+
80+
foldMap :: forall a m. (Monoid m) => (String -> a -> m) -> StrMap a -> m
81+
foldMap f = fold (\acc k v -> acc P.<> f k v) mempty
82+
83+
foldM :: forall a m z. (P.Monad m) => (z -> String -> a -> m z) -> z -> StrMap a -> m z
84+
foldM f z = _foldM P.(>>=) f (P.pure z)
85+
86+
-- Unfortunately the above are not short-circuitable (consider using purescript-machines)
87+
-- so we need special cases:
88+
6489
foreign import _foldSCStrMap
65-
"function _foldSCStrMap(m, z0, f, fromMaybe) { \
66-
\ var z = z0; \
90+
"function _foldSCStrMap(m, z, f, fromMaybe) { \
6791
\ for (var k in m) { \
68-
\ if (m.hasOwnProperty(k)) { \
69-
\ var maybeR = f(z)(k)(m[k]); \
70-
\ var r = fromMaybe(null)(maybeR); \
71-
\ if (r === null) return z; \
72-
\ else z = r; \
73-
\ } \
92+
\ var maybeR = f(z)(k)(m[k]); \
93+
\ var r = fromMaybe(null)(maybeR); \
94+
\ if (r === null) return z; \
95+
\ else z = r; \
7496
\ } \
7597
\ return z; \
7698
\}" :: forall a z. Fn4 (StrMap a) z (z -> String -> a -> Maybe z) (forall a. a -> Maybe a -> a) z
7799

78-
foldMaybe :: forall a z. (z -> String -> a -> Maybe z) -> z -> (StrMap a) -> z
100+
foldMaybe :: forall a z. (z -> String -> a -> Maybe z) -> z -> StrMap a -> z
79101
foldMaybe f z m = runFn4 _foldSCStrMap m z f fromMaybe
80102

103+
foreign import all
104+
"function all(f) {\
105+
\ return function (m) {\
106+
\ for (var k in m)\
107+
\ if (!f(k)(m[k])) return false;\
108+
\ return true;\
109+
\ };\
110+
\}" :: forall a. (String -> a -> Boolean) -> StrMap a -> Boolean
111+
81112
instance eqStrMap :: (P.Eq a) => P.Eq (StrMap a) where
82113
(==) m1 m2 = (isSubmap m1 m2) P.&& (isSubmap m2 m1)
83114
(/=) m1 m2 = P.not (m1 P.== m2)
@@ -88,17 +119,16 @@ instance showStrMap :: (P.Show a) => P.Show (StrMap a) where
88119
foreign import empty "var empty = {};" :: forall a. StrMap a
89120

90121
isSubmap :: forall a. (P.Eq a) => StrMap a -> StrMap a -> Boolean
91-
isSubmap m1 m2 = foldMaybe f true m1 where
92-
f acc k v = if (P.not acc) then (Nothing :: Maybe Boolean)
93-
else Just P.$ acc P.&& (maybe false (\v0 -> v0 P.== v) (lookup k m2))
122+
isSubmap m1 m2 = all f m1 where
123+
f k v = runFn4 _lookup false (P.(==) v) k m2
94124

95125
isEmpty :: forall a. StrMap a -> Boolean
96-
isEmpty m = size m P.== 0
126+
isEmpty = all (\_ _ -> false)
97127

98128
foreign import size "function size(m) {\
99129
\ var s = 0;\
100130
\ for (var k in m) {\
101-
\ if (m.hasOwnProperty(k)) ++s;\
131+
\ ++s;\
102132
\ }\
103133
\ return s;\
104134
\}" :: forall a. StrMap a -> Number
@@ -107,22 +137,21 @@ singleton :: forall a. String -> a -> StrMap a
107137
singleton k v = insert k v empty
108138

109139
foreign import _lookup
110-
"function _lookup(m, k, yes, no) { \
111-
\ if (m[k] !== undefined) return yes(m[k]); \
112-
\ else return no; \
113-
\}" :: forall a z. Fn4 (StrMap a) String (a -> z) z z
140+
"function _lookup(no, yes, k, m) {\
141+
\ return k in m ? yes(m[k]) : no;\
142+
\}" :: forall a z. Fn4 z (a -> z) String (StrMap a) z
114143

115144
lookup :: forall a. String -> StrMap a -> Maybe a
116-
lookup k m = runFn4 _lookup m k Just Nothing
145+
lookup = runFn4 _lookup Nothing Just
117146

118147
member :: forall a. String -> StrMap a -> Boolean
119-
member k m = isJust (k `lookup` m)
148+
member = runFn4 _lookup false (P.const true)
120149

121150
foreign import _cloneStrMap
122151
"function _cloneStrMap(m0) { \
123152
\ var m = {}; \
124153
\ for (var k in m0) {\
125-
\ if (m0.hasOwnProperty(k)) m[k] = m0[k];\
154+
\ m[k] = m0[k];\
126155
\ }\
127156
\ return m;\
128157
\}" :: forall a. (StrMap a) -> (StrMap a)
@@ -133,8 +162,11 @@ foreign import _unsafeInsertStrMap
133162
\ return m; \
134163
\}" :: forall a. Fn3 (StrMap a) String a (StrMap a)
135164

165+
_unsafeInsert :: forall a. StrMap a -> String -> a -> StrMap a
166+
_unsafeInsert = runFn3 _unsafeInsertStrMap
167+
136168
insert :: forall a. String -> a -> StrMap a -> StrMap a
137-
insert k v m = runFn3 _unsafeInsertStrMap (_cloneStrMap m) k v
169+
insert k v m = _unsafeInsert (_cloneStrMap m) k v
138170

139171
foreign import _unsafeDeleteStrMap
140172
"function _unsafeDeleteStrMap(m, k) { \
@@ -153,26 +185,40 @@ alter f k m = case f (k `lookup` m) of
153185
update :: forall a. (a -> Maybe a) -> String -> StrMap a -> StrMap a
154186
update f k m = alter (maybe Nothing f) k m
155187

188+
foreign import _collect
189+
"function _collect(f) {\
190+
\ return function (m) {\
191+
\ var r = [];\
192+
\ for (var k in m)\
193+
\ r.push(f(k)(m[k]));\
194+
\ return r;\
195+
\ };\
196+
\}" :: forall a b . (String -> a -> b) -> StrMap a -> [b]
197+
156198
toList :: forall a. StrMap a -> [Tuple String a]
157-
toList m = fold f [] m where
158-
f acc k v = acc P.++ [Tuple k v]
199+
toList = _collect Tuple
159200

160201
fromList :: forall a. [Tuple String a] -> StrMap a
161-
fromList = foldl (\m (Tuple k v) -> insert k v m) empty
202+
fromList = foldl (\m (Tuple k v) -> _unsafeInsert m k v) (_cloneStrMap empty)
162203

163-
keys :: forall a. StrMap a -> [String]
164-
keys m = fold f [] m where
165-
f acc k v = acc P.++ [k]
204+
foreign import keys
205+
"var keys = Object.keys || _collect(function (k) {\
206+
\ return function () { return k; };\
207+
\});" :: forall a. StrMap a -> [String]
166208

167209
values :: forall a. StrMap a -> [a]
168-
values m = fold f [] m where
169-
f acc k v = acc P.++ [v]
210+
values = _collect (\_ v -> v)
170211

212+
-- left-biased
171213
union :: forall a. StrMap a -> StrMap a -> StrMap a
172-
union m1 m2 = foldl (\m (Tuple k v) -> insert k v m) m2 (toList m1)
214+
union m1 m2 = fold _unsafeInsert (_cloneStrMap m2) m1
173215

174216
unions :: forall a. [StrMap a] -> StrMap a
175217
unions = foldl union empty
176218

177219
map :: forall a b. (a -> b) -> StrMap a -> StrMap b
178-
map = P.(<$>)
220+
map = P.(<$>)
221+
222+
instance semigroupStrMap :: (P.Semigroup a) => P.Semigroup (StrMap a) where
223+
(<>) m1 m2 = fold f (_cloneStrMap m1) m2 where
224+
f m k v2 = _unsafeInsert m k (runFn4 _lookup v2 (\v1 -> v1 P.<> v2) k m)

src/Data/StrMap/Unsafe.purs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Data.StrMap.Unsafe
2+
( unsafeIndex
3+
) where
4+
5+
import Data.StrMap
6+
7+
-- also known as (!)
8+
foreign import unsafeIndex
9+
"function unsafeIndex(m) { \
10+
\ return function (k) {\
11+
\ return m[k];\
12+
\ };\
13+
\}" :: forall a . StrMap a -> String -> a

tests/Data/StrMap.purs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,6 @@ import qualified Data.StrMap as M
1717
instance arbStrMap :: (Arbitrary v) => Arbitrary (M.StrMap v) where
1818
arbitrary = M.fromList <<< map runTestTuple <$> arbitrary
1919

20-
type SmallKey = String
21-
22-
instance arbSmallKey :: Arbitrary String where
23-
arbitrary = do
24-
nums <- arbitrary
25-
return $ S.joinWith "" (S.fromCharCode <$> nums)
26-
2720
data Instruction k v = Insert k v | Delete k
2821

2922
instance showInstruction :: (Show k, Show v) => Show (Instruction k v) where
@@ -33,13 +26,12 @@ instance showInstruction :: (Show k, Show v) => Show (Instruction k v) where
3326
instance arbInstruction :: (Arbitrary v) => Arbitrary (Instruction String v) where
3427
arbitrary = do
3528
b <- arbitrary
29+
k <- arbitrary
3630
case b of
3731
true -> do
38-
k <- arbitrary
3932
v <- arbitrary
4033
return (Insert k v)
4134
false -> do
42-
k <- arbitrary
4335
return (Delete k)
4436

4537
runInstructions :: forall v. [Instruction String v] -> M.StrMap v -> M.StrMap v
@@ -48,38 +40,35 @@ runInstructions instrs t0 = foldl step t0 instrs
4840
step tree (Insert k v) = M.insert k v tree
4941
step tree (Delete k) = M.delete k tree
5042

51-
smallKey :: SmallKey -> SmallKey
52-
smallKey k = k
53-
5443
number :: Number -> Number
5544
number n = n
5645

5746
strMapTests = do
5847
trace "Test inserting into empty tree"
59-
quickCheck $ \k v -> M.lookup (smallKey k) (M.insert k v M.empty) == Just (number v)
48+
quickCheck $ \k v -> M.lookup k (M.insert k v M.empty) == Just (number v)
6049
<?> ("k: " ++ show k ++ ", v: " ++ show v)
6150

6251
trace "Test delete after inserting"
63-
quickCheck $ \k v -> M.isEmpty (M.delete (smallKey k) (M.insert k (number v) M.empty))
52+
quickCheck $ \k v -> M.isEmpty (M.delete k (M.insert k (number v) M.empty))
6453
<?> ("k: " ++ show k ++ ", v: " ++ show v)
6554

6655
trace "Insert two, lookup first"
67-
quickCheck $ \k1 v1 k2 v2 -> k1 == k2 || M.lookup k1 (M.insert (smallKey k2) (number v2) (M.insert (smallKey k1) (number v1) M.empty)) == Just v1
56+
quickCheck $ \k1 v1 k2 v2 -> k1 == k2 || M.lookup k1 (M.insert k2 (number v2) (M.insert k1 (number v1) M.empty)) == Just v1
6857
<?> ("k1: " ++ show k1 ++ ", v1: " ++ show v1 ++ ", k2: " ++ show k2 ++ ", v2: " ++ show v2)
6958

7059
trace "Insert two, lookup second"
71-
quickCheck $ \k1 v1 k2 v2 -> M.lookup k2 (M.insert (smallKey k2) (number v2) (M.insert (smallKey k1) (number v1) M.empty)) == Just v2
60+
quickCheck $ \k1 v1 k2 v2 -> M.lookup k2 (M.insert k2 (number v2) (M.insert k1 (number v1) M.empty)) == Just v2
7261
<?> ("k1: " ++ show k1 ++ ", v1: " ++ show v1 ++ ", k2: " ++ show k2 ++ ", v2: " ++ show v2)
7362

7463
trace "Insert two, delete one"
75-
quickCheck $ \k1 v1 k2 v2 -> k1 == k2 || M.lookup k2 (M.delete k1 (M.insert (smallKey k2) (number v2) (M.insert (smallKey k1) (number v1) M.empty))) == Just v2
64+
quickCheck $ \k1 v1 k2 v2 -> k1 == k2 || M.lookup k2 (M.delete k1 (M.insert k2 (number v2) (M.insert k1 (number v1) M.empty))) == Just v2
7665
<?> ("k1: " ++ show k1 ++ ", v1: " ++ show v1 ++ ", k2: " ++ show k2 ++ ", v2: " ++ show v2)
7766

7867
trace "Lookup from empty"
7968
quickCheck $ \k -> M.lookup k (M.empty :: M.StrMap Number) == Nothing
8069

8170
trace "Lookup from singleton"
82-
quickCheck $ \k v -> M.lookup (k :: SmallKey) (M.singleton k (v :: Number)) == Just v
71+
quickCheck $ \k v -> M.lookup k (M.singleton k (v :: Number)) == Just v
8372

8473
trace "Random lookup"
8574
quickCheck' 5000 $ \instrs k v ->
@@ -94,16 +83,19 @@ strMapTests = do
9483
trace "toList . fromList = id"
9584
quickCheck $ \arr -> let f x = M.toList (M.fromList x)
9685
arr' = runTestTuple <$> arr
97-
in f (f arr') == f (arr' :: [Tuple SmallKey Number]) <?> show arr
86+
in f (f arr') == f (arr' :: [Tuple String Number]) <?> show arr
9887

9988
trace "fromList . toList = id"
10089
quickCheck $ \m -> let f m = M.fromList (M.toList m) in
10190
M.toList (f m) == M.toList (m :: M.StrMap Number) <?> show m
10291

10392
trace "Lookup from union"
104-
quickCheck $ \m1 m2 k -> M.lookup (smallKey k) (M.union m1 m2) == (case M.lookup k m1 of
93+
quickCheck $ \m1 m2 k -> M.lookup k (M.union m1 m2) == (case M.lookup k m1 of
10594
Nothing -> M.lookup k m2
10695
Just v -> Just (number v)) <?> ("m1: " ++ show m1 ++ ", m2: " ++ show m2 ++ ", k: " ++ show k ++ ", v1: " ++ show (M.lookup k m1) ++ ", v2: " ++ show (M.lookup k m2) ++ ", union: " ++ show (M.union m1 m2))
10796

10897
trace "Union is idempotent"
109-
quickCheck $ \m1 m2 -> (m1 `M.union` m2) == ((m1 `M.union` m2) `M.union` (m2 :: M.StrMap Number))
98+
quickCheck $ \m1 m2 -> (m1 `M.union` m2) == ((m1 `M.union` m2) `M.union` (m2 :: M.StrMap Number)) <?> (show (M.size (m1 `M.union` m2)) ++ " != " ++ show (M.size ((m1 `M.union` m2) `M.union` m2)))
99+
100+
trace "toList = zip keys values"
101+
quickCheck $ \m -> M.toList m == zip (M.keys m) (M.values m :: [Number])

0 commit comments

Comments
 (0)