@grain/stdlib 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ### [0.5.4](https://github.com/grain-lang/grain/compare/stdlib-v0.5.3...stdlib-v0.5.4) (2022-11-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * **stdlib:** Add `empty` constant to ImmutablePriorityQueue module ([427335f](https://github.com/grain-lang/grain/commit/427335fa5c211445f727a650ca06adacfe9c5310))
9
+ * **stdlib:** Add `empty` constant to Queue module ([427335f](https://github.com/grain-lang/grain/commit/427335fa5c211445f727a650ca06adacfe9c5310))
10
+ * **stdlib:** Add `empty` constant to Stack module ([427335f](https://github.com/grain-lang/grain/commit/427335fa5c211445f727a650ca06adacfe9c5310))
11
+ * **stdlib:** Add `exp` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
12
+ * **stdlib:** Add `factorial` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
13
+ * **stdlib:** Add `gamma` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
14
+ * **stdlib:** Add `infinity` constant to the Number module ([c24f6c1](https://github.com/grain-lang/grain/commit/c24f6c1cfae87632a003c0337c29ec98a80cfda2))
15
+ * **stdlib:** Add `nan` constant to the Number module ([c24f6c1](https://github.com/grain-lang/grain/commit/c24f6c1cfae87632a003c0337c29ec98a80cfda2))
16
+ * **stdlib:** Add `pow` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
17
+ * **stdlib:** Add `replaceAll` function to String module ([5606cd2](https://github.com/grain-lang/grain/commit/5606cd246583884175b135cbeb29024400651b34))
18
+ * **stdlib:** Add `replaceFirst` function to String module ([5606cd2](https://github.com/grain-lang/grain/commit/5606cd246583884175b135cbeb29024400651b34))
19
+ * **stdlib:** Add `replaceLast` function to String module ([5606cd2](https://github.com/grain-lang/grain/commit/5606cd246583884175b135cbeb29024400651b34))
20
+ * **stdlib:** Add `tan` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
21
+ * **stdlib:** Add `toDegrees` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
22
+ * **stdlib:** Add `toRadians` function to Number module ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
23
+ * **stdlib:** Add additional functions to Number module ([#1443](https://github.com/grain-lang/grain/issues/1443)) ([5af9a99](https://github.com/grain-lang/grain/commit/5af9a99b2ec3b4a2d6745cb22b70defe2b366cfa))
24
+ * **stdlib:** Add replacement functions to String module ([#1441](https://github.com/grain-lang/grain/issues/1441)) ([5606cd2](https://github.com/grain-lang/grain/commit/5606cd246583884175b135cbeb29024400651b34))
25
+ * **stdlib:** Added `empty` constant to immutable data structures ([#1466](https://github.com/grain-lang/grain/issues/1466)) ([427335f](https://github.com/grain-lang/grain/commit/427335fa5c211445f727a650ca06adacfe9c5310))
26
+ * **stdlib:** Implement `fromArray` in PriorityQueue & ImmutablePriorityQueue modules ([#1451](https://github.com/grain-lang/grain/issues/1451)) ([d321f84](https://github.com/grain-lang/grain/commit/d321f84174fee2a340745a9f55994fbfa23f6c7a))
27
+ * **stdlib:** Implement ImmutableMap and ImmutableSet ([#1414](https://github.com/grain-lang/grain/issues/1414)) ([b31120d](https://github.com/grain-lang/grain/commit/b31120d41be668c48b9bca9f2b944616371a8ab4))
28
+ * **stdlib:** Improved efficiency of constructing a PriorityQueue from a List ([d321f84](https://github.com/grain-lang/grain/commit/d321f84174fee2a340745a9f55994fbfa23f6c7a))
29
+ * **stdlib:** Optimize string trimming ([#1442](https://github.com/grain-lang/grain/issues/1442)) ([0212247](https://github.com/grain-lang/grain/commit/0212247a7fbf0d54085959de2853f3fe66cd8b12))
30
+
31
+
32
+ ### Bug Fixes
33
+
34
+ * **compiler:** Panic immediately when out of memory ([#1450](https://github.com/grain-lang/grain/issues/1450)) ([943d47d](https://github.com/grain-lang/grain/commit/943d47dddde2d88fd96727e9d7ed8501efec42ef))
35
+ * **grainfmt:** Handle chained value bindings properly ([#1467](https://github.com/grain-lang/grain/issues/1467)) ([07bfcd3](https://github.com/grain-lang/grain/commit/07bfcd3f15c34ef99b05531591b1473f206b7395))
36
+ * **grainfmt:** Indent lines when wrapping infix operators ([#1465](https://github.com/grain-lang/grain/issues/1465)) ([d705849](https://github.com/grain-lang/grain/commit/d705849ea8d9073e608576b77adeae834c454e0b))
37
+ * **runtime:** Handle bigint mul/div within Number correctly ([#1475](https://github.com/grain-lang/grain/issues/1475)) ([0fe8aa6](https://github.com/grain-lang/grain/commit/0fe8aa6a96a9c5ebf2f2bf2e1f28578badfb337f))
38
+ * **stdlib:** Fix anchoring behavior in Regex.replaceAll ([#1440](https://github.com/grain-lang/grain/issues/1440)) ([d513eff](https://github.com/grain-lang/grain/commit/d513effe569d0aa0d44c974596fd285f1ad8d57d))
39
+ * **stdlib:** Fix handling of `NaN` and `Infinity` in Number module ([#1457](https://github.com/grain-lang/grain/issues/1457)) ([c24f6c1](https://github.com/grain-lang/grain/commit/c24f6c1cfae87632a003c0337c29ec98a80cfda2))
40
+
3
41
  ### [0.5.3](https://github.com/grain-lang/grain/compare/stdlib-v0.5.2...stdlib-v0.5.3) (2022-08-05)
4
42
 
5
43
 
package/array.gr CHANGED
@@ -48,6 +48,7 @@ let initLength = length => {
48
48
  * @param array: The array to inspect
49
49
  * @returns The number of elements in the array
50
50
  *
51
+ * @example Array.length([> 1, 2, 3, 4, 5]) == 5
51
52
  * @since v0.1.0
52
53
  */
53
54
  @disableGC
@@ -145,6 +146,7 @@ export let rec init /*: (Number, Number -> a) -> Array<a>*/ =
145
146
  * @param index: The index to access
146
147
  * @param array: The array to access
147
148
  * @returns The element from the array
149
+ * @example Array.get(1,[> 1, 2, 3, 4, 5]) == 2
148
150
  *
149
151
  * @since v0.1.0
150
152
  * @history v0.2.0: Argument order changed to data-last
@@ -162,6 +164,7 @@ export let get = (index, array) => {
162
164
  * @param index: The index to update
163
165
  * @param value: The value to store
164
166
  * @param array: The array to update
167
+ * @example Array.set(1, 9, [> 1, 2, 3, 4, 5]) == [> 1, 9, 3, 4, 5]
165
168
  *
166
169
  * @since v0.1.0
167
170
  * @history v0.2.0: Argument order changed to data-last
@@ -177,6 +180,7 @@ export let set = (index, value, array) => {
177
180
  * @param array1: The array containing elements to appear first
178
181
  * @param array2: The array containing elements to appear second
179
182
  * @returns The new array containing elements from `array1` followed by elements from `array2`
183
+ * @example Array.append([> 1, 2], [> 3, 4, 5]) == [> 1, 2, 3, 4, 5]
180
184
  *
181
185
  * @since v0.1.0
182
186
  */
@@ -199,6 +203,7 @@ export let append = (array1, array2) => {
199
203
  *
200
204
  * @param arrays: A list containing all arrays to combine
201
205
  * @returns The new array
206
+ * @example Array.concat([[> 1, 2], [> 3, 4], [> 5, 6]]) == [> 1, 2, 3, 4, 5, 6]
202
207
  *
203
208
  * @since v0.1.0
204
209
  */
package/array.md CHANGED
@@ -50,6 +50,12 @@ Returns:
50
50
  |----|-----------|
51
51
  |`Number`|The number of elements in the array|
52
52
 
53
+ Examples:
54
+
55
+ ```grain
56
+ Array.length([> 1, 2, 3, 4, 5]) == 5
57
+ ```
58
+
53
59
  ### Array.**make**
54
60
 
55
61
  <details disabled>
@@ -153,6 +159,12 @@ Returns:
153
159
  |----|-----------|
154
160
  |`a`|The element from the array|
155
161
 
162
+ Examples:
163
+
164
+ ```grain
165
+ Array.get(1,[> 1, 2, 3, 4, 5]) == 2
166
+ ```
167
+
156
168
  ### Array.**set**
157
169
 
158
170
  <details>
@@ -184,6 +196,12 @@ Parameters:
184
196
  |`value`|`a`|The value to store|
185
197
  |`array`|`Array<a>`|The array to update|
186
198
 
199
+ Examples:
200
+
201
+ ```grain
202
+ Array.set(1, 9, [> 1, 2, 3, 4, 5]) == [> 1, 9, 3, 4, 5]
203
+ ```
204
+
187
205
  ### Array.**append**
188
206
 
189
207
  <details disabled>
@@ -211,6 +229,12 @@ Returns:
211
229
  |----|-----------|
212
230
  |`Array<a>`|The new array containing elements from `array1` followed by elements from `array2`|
213
231
 
232
+ Examples:
233
+
234
+ ```grain
235
+ Array.append([> 1, 2], [> 3, 4, 5]) == [> 1, 2, 3, 4, 5]
236
+ ```
237
+
214
238
  ### Array.**concat**
215
239
 
216
240
  <details disabled>
@@ -237,6 +261,12 @@ Returns:
237
261
  |----|-----------|
238
262
  |`Array<a>`|The new array|
239
263
 
264
+ Examples:
265
+
266
+ ```grain
267
+ Array.concat([[> 1, 2], [> 3, 4], [> 5, 6]]) == [> 1, 2, 3, 4, 5, 6]
268
+ ```
269
+
240
270
  ### Array.**copy**
241
271
 
242
272
  <details disabled>
package/char.gr CHANGED
@@ -47,8 +47,8 @@ export let max = 0x10FFFF
47
47
  */
48
48
  export let isValid = charCode => {
49
49
  charCode >= min &&
50
- (charCode <= 0xD7FF || charCode >= 0xE000) &&
51
- charCode <= max
50
+ (charCode <= 0xD7FF || charCode >= 0xE000) &&
51
+ charCode <= max
52
52
  }
53
53
 
54
54
  /**
@@ -0,0 +1,493 @@
1
+ /**
2
+ * @module ImmutableMap: An ImmutableMap holds key-value pairs. Any value may be used as a key or value. Operations on an ImmutableMap do not mutate the map's internal state.
3
+ * @example import ImmutableMap from "immutablemap"
4
+ *
5
+ * @since v0.5.4
6
+ */
7
+
8
+ import List from "list"
9
+ import Array from "array"
10
+ import Option from "option"
11
+
12
+ // implementation based on the paper "Implementing Sets Efficiently in a
13
+ // Functional Language" by Stephen Adams
14
+ record Node<k, v> {
15
+ key: k,
16
+ val: v,
17
+ size: Number,
18
+ left: ImmutableMap<k, v>,
19
+ right: ImmutableMap<k, v>,
20
+ },
21
+ /**
22
+ * @section Types: Type declarations included in the ImmutableMap module.
23
+ */
24
+ enum ImmutableMap<k, v> {
25
+ Empty,
26
+ Tree(Node<k, v>),
27
+ }
28
+
29
+ /**
30
+ * @section Values: Functions and constants for working with ImmutableMaps.
31
+ */
32
+
33
+ // semi-arbitrary value chosen for algorithm for determining when to balance
34
+ // trees; no tree can have a left subtree containing this number of times
35
+ // more elements than its right subtree or vice versa
36
+ let weight = 4
37
+
38
+ /**
39
+ * An empty map
40
+ *
41
+ * @since v0.5.4
42
+ */
43
+ export let empty = Empty
44
+
45
+ // returns the key-value pair of the minimum key in a tree
46
+ let rec min = node => {
47
+ match (node) {
48
+ Tree({ key, val, left: Empty, _ }) => (key, val),
49
+ Tree({ left, _ }) => min(left),
50
+ Empty => fail "Impossible: min of empty element in ImmutableMap",
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Provides the count of key-value pairs stored within the map.
56
+ *
57
+ * @param map: The map to inspect
58
+ * @returns The count of key-value pairs in the map
59
+ *
60
+ * @since v0.5.4
61
+ */
62
+ export let size = map => {
63
+ match (map) {
64
+ Empty => 0,
65
+ Tree({ size, _ }) => size,
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Determines if the map contains no key-value pairs.
71
+ *
72
+ * @param map: The map to inspect
73
+ * @returns `true` if the given map is empty or `false` otherwise
74
+ *
75
+ * @since v0.5.4
76
+ */
77
+ export let isEmpty = map => {
78
+ match (map) {
79
+ Empty => true,
80
+ Tree(_) => false,
81
+ }
82
+ }
83
+
84
+ let unwrapTree = node => {
85
+ match (node) {
86
+ Empty => fail "Impossible: ImmutableMap unwrapTree got an empty tree node",
87
+ Tree(tree) => tree,
88
+ }
89
+ }
90
+
91
+ // helper function for creating a tree node with correct size from
92
+ // two balanced trees
93
+ let makeNode = (key, val, left, right) => {
94
+ Tree({ key, val, size: 1 + size(left) + size(right), left, right })
95
+ }
96
+
97
+ // note: see Figure 1 of paper referenced above for visual illustration of
98
+ // the rotations below
99
+
100
+ // node rotation moving the left subtree of the right node to the left side
101
+ let singleL = (key, val, left, right) => {
102
+ let { key: rKey, val: rVal, left: rl, right: rr, _ } = unwrapTree(right)
103
+ makeNode(rKey, rVal, makeNode(key, val, left, rl), rr)
104
+ }
105
+
106
+ // node rotation moving left child of right tree to the root
107
+ let doubleL = (key, val, left, right) => {
108
+ let { key: rKey, val: rVal, left: rl, right: rr, _ } = unwrapTree(right)
109
+ let { key: rlKey, val: rlVal, left: rll, right: rlr, _ } = unwrapTree(rl)
110
+ makeNode(
111
+ rlKey,
112
+ rlVal,
113
+ makeNode(key, val, left, rll),
114
+ makeNode(rKey, rVal, rlr, rr)
115
+ )
116
+ }
117
+
118
+ // node rotation moving the right subtree of the left node to the right side
119
+ let singleR = (key, val, left, right) => {
120
+ let { key: lKey, val: lVal, left: ll, right: lr, _ } = unwrapTree(left)
121
+ makeNode(lKey, lVal, ll, makeNode(key, val, lr, right))
122
+ }
123
+
124
+ // node rotation moving right child of left tree to the root
125
+ let doubleR = (key, val, left, right) => {
126
+ let { key: lKey, val: lVal, left: ll, right: lr, _ } = unwrapTree(left)
127
+ let { key: lrKey, val: lrVal, left: lrl, right: lrr, _ } = unwrapTree(lr)
128
+ makeNode(
129
+ lrKey,
130
+ lrVal,
131
+ makeNode(lKey, lVal, ll, lrl),
132
+ makeNode(key, val, lrr, right)
133
+ )
134
+ }
135
+
136
+ // creates a new node after either the left or right trees have just had an
137
+ // element inserted or removed from them, maintaining balance in the tree
138
+ let balancedNode = (key, val, left, right) => {
139
+ let makeNodeFn = if (size(left) + size(right) < 2) {
140
+ makeNode
141
+ } else if (size(right) > weight * size(left)) {
142
+ // if the right tree is too much larger than the left then move part of
143
+ // the right tree to the left side
144
+ let { left: rl, right: rr, _ } = unwrapTree(right)
145
+ if (size(rl) < size(rr)) singleL else doubleL
146
+ } else if (size(left) > weight * size(right)) {
147
+ // if the left tree is too much larger than the right then move part of
148
+ // the left tree to the right side
149
+ let { left: ll, right: lr, _ } = unwrapTree(left)
150
+ if (size(lr) < size(ll)) singleR else doubleR
151
+ } else {
152
+ // if neither tree is too much larger than the other then simply create
153
+ // a new node
154
+ makeNode
155
+ }
156
+
157
+ makeNodeFn(key, val, left, right)
158
+ }
159
+
160
+ /**
161
+ * Produces a new map containing a new key-value pair. If the key already exists in the map, the value is replaced.
162
+ *
163
+ * @param key: The unique key in the map
164
+ * @param value: The value to store
165
+ * @param map: The base map
166
+ * @returns A new map containing the new key-value pair
167
+ *
168
+ * @since v0.5.4
169
+ */
170
+ export let rec set = (key, val, map) => {
171
+ match (map) {
172
+ Empty => Tree({ key, val, size: 1, left: Empty, right: Empty }),
173
+ Tree({ key: nodeKey, val: nodeVal, left, right, _ }) => {
174
+ match (compare(key, nodeKey)) {
175
+ cmp when cmp < 0 =>
176
+ balancedNode(nodeKey, nodeVal, set(key, val, left), right),
177
+ cmp when cmp > 0 =>
178
+ balancedNode(nodeKey, nodeVal, left, set(key, val, right)),
179
+ _ => makeNode(key, val, left, right),
180
+ }
181
+ },
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Retrieves the value for the given key.
187
+ *
188
+ * @param key: The key to access
189
+ * @param map: The map to access
190
+ * @returns `Some(value)` if the key exists in the map or `None` otherwise
191
+ *
192
+ * @since v0.5.4
193
+ */
194
+ export let rec get = (key, map) => {
195
+ match (map) {
196
+ Empty => None,
197
+ Tree({ key: nodeKey, val: nodeVal, left, right, _ }) => {
198
+ match (compare(key, nodeKey)) {
199
+ cmp when cmp < 0 => get(key, left),
200
+ cmp when cmp > 0 => get(key, right),
201
+ _ => Some(nodeVal),
202
+ }
203
+ },
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Determines if the map contains the given key. In such a case, it will always contain a value for the given key.
209
+ *
210
+ * @param key: The key to search for
211
+ * @param map: The map to search
212
+ * @returns `true` if the map contains the given key or `false` otherwise
213
+ *
214
+ * @since v0.5.4
215
+ */
216
+ export let rec contains = (key, map) => {
217
+ Option.isSome(get(key, map))
218
+ }
219
+
220
+ // removes the minimum element from a tree
221
+ let rec removeMin = node => {
222
+ match (node) {
223
+ Tree({ left: Empty, right, _ }) => right,
224
+ Tree({ key, val, left, right, _ }) =>
225
+ balancedNode(key, val, removeMin(left), right),
226
+ _ => fail "Impossible: ImmutableMap removeMin on empty node",
227
+ }
228
+ }
229
+
230
+ // helper function for removing a node by creating a new node containing the
231
+ // removed node's left and right subtrees
232
+ let removeInner = (left, right) => {
233
+ match ((left, right)) {
234
+ (Empty, node) | (node, Empty) => node,
235
+ (left, right) => {
236
+ let (minKey, minVal) = min(right)
237
+ balancedNode(minKey, minVal, left, removeMin(right))
238
+ },
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Produces a new map without the key-value pair corresponding to the given
244
+ * key. If the key doesn't exist in the map, the map will be returned unmodified.
245
+ *
246
+ * @param key: The key to exclude
247
+ * @param map: The map to exclude from
248
+ * @returns A new map without the given key
249
+ *
250
+ * @since v0.5.4
251
+ */
252
+ export let rec remove = (key, map) => {
253
+ match (map) {
254
+ Empty => Empty,
255
+ Tree({ key: nodeKey, val: nodeVal, left, right, _ }) => {
256
+ match (compare(key, nodeKey)) {
257
+ cmp when cmp < 0 =>
258
+ balancedNode(nodeKey, nodeVal, remove(key, left), right),
259
+ cmp when cmp > 0 =>
260
+ balancedNode(nodeKey, nodeVal, left, remove(key, right)),
261
+ _ => removeInner(left, right),
262
+ }
263
+ },
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Produces a new map by calling an updater function that receives the
269
+ * previously stored value as an `Option` and returns the new value to be
270
+ * stored as an `Option`. If the key didn't exist previously, the value
271
+ * will be `None`. If `None` is returned from the updater function, the
272
+ * key-value pair is excluded.
273
+ *
274
+ * @param key: The unique key in the map
275
+ * @param fn: The updater function
276
+ * @param map: The base map
277
+ * @returns A new map with the value at the given key modified according to the function's output
278
+ *
279
+ * @since v0.5.4
280
+ */
281
+ export let update = (key, fn, map) => {
282
+ let val = get(key, map)
283
+ match (fn(val)) {
284
+ Some(next) => set(key, next, map),
285
+ None => remove(key, map),
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Iterates the map, calling an iterator function with each key and value.
291
+ *
292
+ * @param fn: The iterator function to call with each key and value
293
+ * @param map: The map to iterate
294
+ *
295
+ * @since v0.5.4
296
+ */
297
+ export let forEach = (fn, map) => {
298
+ let rec forEachInner = node => {
299
+ match (node) {
300
+ Empty => void,
301
+ Tree({ key, val, left, right, _ }) => {
302
+ forEachInner(left)
303
+ fn(key, val): Void
304
+ forEachInner(right)
305
+ },
306
+ }
307
+ }
308
+ forEachInner(map)
309
+ }
310
+
311
+ /**
312
+ * Combines all key-value pairs of a map using a reducer function.
313
+ *
314
+ * @param fn: The reducer function to call on each key and value, where the value returned will be the next accumulator value
315
+ * @param init: The initial value to use for the accumulator on the first iteration
316
+ * @param map: The map to iterate
317
+ * @returns The final accumulator returned from `fn`
318
+ *
319
+ * @since v0.5.4
320
+ */
321
+ export let reduce = (fn, init, map) => {
322
+ let rec reduceInner = (acc, node) => {
323
+ match (node) {
324
+ Empty => acc,
325
+ Tree({ key, val, left, right, _ }) => {
326
+ let newAcc = fn(reduceInner(acc, left), key, val)
327
+ reduceInner(newAcc, right)
328
+ },
329
+ }
330
+ }
331
+ reduceInner(init, map)
332
+ }
333
+
334
+ // joins two trees with a value, preserving the BST property of left children
335
+ // being less the node and right children being greater than the node
336
+ let rec concat3 = (key, val, left, right) => {
337
+ match ((left, right)) {
338
+ (Empty, node) | (node, Empty) => set(key, val, node),
339
+ (Tree(left) as leftOpt, Tree(right) as rightOpt) => {
340
+ if (weight * left.size < right.size) {
341
+ balancedNode(
342
+ right.key,
343
+ right.val,
344
+ concat3(key, val, leftOpt, right.left),
345
+ right.right
346
+ )
347
+ } else if (weight * right.size < left.size) {
348
+ balancedNode(
349
+ left.key,
350
+ left.val,
351
+ left.left,
352
+ concat3(key, val, left.right, rightOpt)
353
+ )
354
+ } else {
355
+ makeNode(key, val, leftOpt, rightOpt)
356
+ }
357
+ },
358
+ }
359
+ }
360
+
361
+ // concatenates two trees of arbitrary size
362
+ let concat = (node1, node2) => {
363
+ match (node2) {
364
+ Empty => node1,
365
+ _ => {
366
+ let (minKey, minVal) = min(node2)
367
+ concat3(minKey, minVal, node1, removeMin(node2))
368
+ },
369
+ }
370
+ }
371
+
372
+ let reduceRight = (fn, init, map) => {
373
+ let rec reduceInner = (acc, node) => {
374
+ match (node) {
375
+ Empty => acc,
376
+ Tree({ key, val, left, right, _ }) => {
377
+ let newAcc = fn(reduceInner(acc, right), key, val)
378
+ reduceInner(newAcc, left)
379
+ },
380
+ }
381
+ }
382
+ reduceInner(init, map)
383
+ }
384
+
385
+ /**
386
+ * Enumerates all keys in the given map.
387
+ *
388
+ * @param map: The map to enumerate
389
+ * @returns A list containing all keys from the given map
390
+ *
391
+ * @since v0.5.4
392
+ */
393
+ export let keys = map => {
394
+ reduceRight((list, key, _) => [key, ...list], [], map)
395
+ }
396
+
397
+ /**
398
+ * Enumerates all values in the given map.
399
+ *
400
+ * @param map: The map to enumerate
401
+ * @returns A list containing all values from the given map
402
+ *
403
+ * @since v0.5.4
404
+ */
405
+ export let values = map => {
406
+ reduceRight((list, _, value) => [value, ...list], [], map)
407
+ }
408
+
409
+ /**
410
+ * Produces a new map excluding the key-value pairs where a predicate function returns `false`.
411
+ *
412
+ * @param fn: The predicate function to indicate which key-value pairs to exclude from the map, where returning `false` indicates the key-value pair should be excluded
413
+ * @param map: The map to iterate
414
+ * @returns A new map excluding the key-value pairs not fulfilling the predicate
415
+ *
416
+ * @since v0.5.4
417
+ */
418
+ export let filter = (fn, map) => {
419
+ let rec filterInner = node => {
420
+ match (node) {
421
+ Empty => Empty,
422
+ Tree({ key, val, left, right, _ }) => {
423
+ if (fn(key, val)) {
424
+ concat3(key, val, filterInner(left), filterInner(right))
425
+ } else {
426
+ concat(filterInner(left), filterInner(right))
427
+ }
428
+ },
429
+ }
430
+ }
431
+ filterInner(map)
432
+ }
433
+
434
+ /**
435
+ * Produces a new map excluding the key-value pairs where a predicate function returns `true`.
436
+ *
437
+ * @param fn: The predicate function to indicate which key-value pairs to exclude from the map, where returning `true` indicates the key-value pair should be excluded
438
+ * @param map: The map to iterate
439
+ * @returns A new map excluding the key-value pairs fulfilling the predicate
440
+ *
441
+ * @since v0.5.4
442
+ */
443
+ export let reject = (fn, map) => {
444
+ filter((key, val) => !fn(key, val), map)
445
+ }
446
+
447
+ /**
448
+ * Creates a map from a list.
449
+ *
450
+ * @param list: The list to convert
451
+ * @returns A map containing all key-value pairs from the list
452
+ *
453
+ * @since v0.5.4
454
+ */
455
+ export let fromList = list => {
456
+ List.reduce((map, (key, val)) => set(key, val, map), empty, list)
457
+ }
458
+
459
+ /**
460
+ * Enumerates all key-value pairs in the given map.
461
+ *
462
+ * @param map: The map to enumerate
463
+ * @returns A list containing all key-value pairs from the given map
464
+ *
465
+ * @since v0.5.4
466
+ */
467
+ export let toList = map => {
468
+ reduceRight((list, key, val) => [(key, val), ...list], [], map)
469
+ }
470
+
471
+ /**
472
+ * Creates a map from an array.
473
+ *
474
+ * @param array: The array to convert
475
+ * @returns A map containing all key-value pairs from the array
476
+ *
477
+ * @since v0.5.4
478
+ */
479
+ export let fromArray = array => {
480
+ Array.reduce((map, (key, val)) => set(key, val, map), empty, array)
481
+ }
482
+
483
+ /**
484
+ * Converts a map into an array of its key-value pairs.
485
+ *
486
+ * @param map: The map to convert
487
+ * @returns An array containing all key-value pairs from the given map
488
+ *
489
+ * @since v0.5.4
490
+ */
491
+ export let toArray = map => {
492
+ Array.fromList(toList(map))
493
+ }