@grain/stdlib 0.5.13 → 0.6.0

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.
Files changed (155) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/LICENSE +1 -1
  3. package/README.md +25 -2
  4. package/array.gr +1512 -199
  5. package/array.md +2032 -94
  6. package/bigint.gr +239 -140
  7. package/bigint.md +450 -106
  8. package/buffer.gr +595 -102
  9. package/buffer.md +903 -145
  10. package/bytes.gr +401 -110
  11. package/bytes.md +551 -63
  12. package/char.gr +228 -49
  13. package/char.md +373 -7
  14. package/exception.gr +26 -12
  15. package/exception.md +29 -5
  16. package/float32.gr +130 -109
  17. package/float32.md +185 -57
  18. package/float64.gr +112 -99
  19. package/float64.md +185 -57
  20. package/hash.gr +47 -37
  21. package/hash.md +21 -3
  22. package/int16.gr +430 -0
  23. package/int16.md +618 -0
  24. package/int32.gr +200 -269
  25. package/int32.md +254 -289
  26. package/int64.gr +142 -225
  27. package/int64.md +254 -289
  28. package/int8.gr +511 -0
  29. package/int8.md +786 -0
  30. package/json.gr +2084 -0
  31. package/json.md +608 -0
  32. package/list.gr +120 -68
  33. package/list.md +125 -80
  34. package/map.gr +560 -57
  35. package/map.md +672 -56
  36. package/marshal.gr +239 -227
  37. package/marshal.md +36 -4
  38. package/number.gr +626 -676
  39. package/number.md +738 -153
  40. package/option.gr +33 -35
  41. package/option.md +58 -42
  42. package/package.json +2 -2
  43. package/path.gr +148 -187
  44. package/path.md +47 -96
  45. package/pervasives.gr +75 -416
  46. package/pervasives.md +85 -180
  47. package/priorityqueue.gr +433 -74
  48. package/priorityqueue.md +422 -54
  49. package/queue.gr +362 -80
  50. package/queue.md +433 -38
  51. package/random.gr +67 -75
  52. package/random.md +68 -40
  53. package/range.gr +135 -63
  54. package/range.md +198 -43
  55. package/rational.gr +284 -0
  56. package/rational.md +545 -0
  57. package/regex.gr +933 -1066
  58. package/regex.md +59 -60
  59. package/result.gr +23 -25
  60. package/result.md +54 -39
  61. package/runtime/atof/common.gr +78 -82
  62. package/runtime/atof/common.md +22 -10
  63. package/runtime/atof/decimal.gr +102 -127
  64. package/runtime/atof/decimal.md +28 -7
  65. package/runtime/atof/lemire.gr +56 -71
  66. package/runtime/atof/lemire.md +9 -1
  67. package/runtime/atof/parse.gr +83 -110
  68. package/runtime/atof/parse.md +12 -2
  69. package/runtime/atof/slow.gr +28 -35
  70. package/runtime/atof/slow.md +9 -1
  71. package/runtime/atof/table.gr +19 -18
  72. package/runtime/atof/table.md +10 -2
  73. package/runtime/atoi/parse.gr +153 -136
  74. package/runtime/atoi/parse.md +50 -1
  75. package/runtime/bigint.gr +410 -517
  76. package/runtime/bigint.md +71 -57
  77. package/runtime/compare.gr +176 -85
  78. package/runtime/compare.md +31 -1
  79. package/runtime/dataStructures.gr +144 -32
  80. package/runtime/dataStructures.md +267 -31
  81. package/runtime/debugPrint.gr +34 -15
  82. package/runtime/debugPrint.md +37 -5
  83. package/runtime/equal.gr +53 -52
  84. package/runtime/equal.md +30 -1
  85. package/runtime/exception.gr +38 -47
  86. package/runtime/exception.md +10 -8
  87. package/runtime/gc.gr +23 -152
  88. package/runtime/gc.md +13 -17
  89. package/runtime/malloc.gr +31 -31
  90. package/runtime/malloc.md +11 -3
  91. package/runtime/numberUtils.gr +191 -172
  92. package/runtime/numberUtils.md +17 -9
  93. package/runtime/numbers.gr +1695 -1021
  94. package/runtime/numbers.md +1098 -134
  95. package/runtime/string.gr +540 -242
  96. package/runtime/string.md +76 -6
  97. package/runtime/unsafe/constants.gr +30 -13
  98. package/runtime/unsafe/constants.md +80 -0
  99. package/runtime/unsafe/conv.gr +55 -28
  100. package/runtime/unsafe/conv.md +41 -9
  101. package/runtime/unsafe/memory.gr +10 -30
  102. package/runtime/unsafe/memory.md +15 -19
  103. package/runtime/unsafe/tags.gr +37 -21
  104. package/runtime/unsafe/tags.md +88 -8
  105. package/runtime/unsafe/wasmf32.gr +30 -36
  106. package/runtime/unsafe/wasmf32.md +64 -56
  107. package/runtime/unsafe/wasmf64.gr +30 -36
  108. package/runtime/unsafe/wasmf64.md +64 -56
  109. package/runtime/unsafe/wasmi32.gr +49 -66
  110. package/runtime/unsafe/wasmi32.md +102 -94
  111. package/runtime/unsafe/wasmi64.gr +52 -79
  112. package/runtime/unsafe/wasmi64.md +108 -100
  113. package/runtime/utils/printing.gr +13 -15
  114. package/runtime/utils/printing.md +11 -3
  115. package/runtime/wasi.gr +294 -295
  116. package/runtime/wasi.md +62 -42
  117. package/set.gr +574 -64
  118. package/set.md +634 -54
  119. package/stack.gr +181 -64
  120. package/stack.md +271 -42
  121. package/string.gr +453 -533
  122. package/string.md +241 -151
  123. package/uint16.gr +369 -0
  124. package/uint16.md +585 -0
  125. package/uint32.gr +470 -0
  126. package/uint32.md +737 -0
  127. package/uint64.gr +471 -0
  128. package/uint64.md +737 -0
  129. package/uint8.gr +369 -0
  130. package/uint8.md +585 -0
  131. package/uri.gr +1093 -0
  132. package/uri.md +477 -0
  133. package/{sys → wasi}/file.gr +914 -500
  134. package/{sys → wasi}/file.md +454 -50
  135. package/wasi/process.gr +292 -0
  136. package/{sys → wasi}/process.md +164 -6
  137. package/wasi/random.gr +77 -0
  138. package/wasi/random.md +80 -0
  139. package/{sys → wasi}/time.gr +15 -22
  140. package/{sys → wasi}/time.md +5 -5
  141. package/immutablearray.gr +0 -929
  142. package/immutablearray.md +0 -1038
  143. package/immutablemap.gr +0 -493
  144. package/immutablemap.md +0 -479
  145. package/immutablepriorityqueue.gr +0 -360
  146. package/immutablepriorityqueue.md +0 -291
  147. package/immutableset.gr +0 -498
  148. package/immutableset.md +0 -449
  149. package/runtime/debug.gr +0 -2
  150. package/runtime/debug.md +0 -6
  151. package/runtime/unsafe/errors.gr +0 -36
  152. package/runtime/unsafe/errors.md +0 -204
  153. package/sys/process.gr +0 -254
  154. package/sys/random.gr +0 -79
  155. package/sys/random.md +0 -66
package/map.gr CHANGED
@@ -1,60 +1,60 @@
1
1
  /**
2
- * @module Map: A Map holds key-value pairs. Any value may be used as a key or value. Operations on a Map mutate the internal state, so it never needs to be re-assigned.
3
- * @example import Map from "map"
2
+ * A Map holds key-value pairs. Any value may be used as a key or value. Operations on a Map mutate the internal state, so it never needs to be re-assigned.
3
+ *
4
+ * An immutable map implementation is available in the `Immutable` submodule.
5
+ * @example from "map" include Map
4
6
  *
5
7
  * @since v0.2.0
6
8
  */
7
- import List from "list"
8
- import Array from "array"
9
- import { hash } from "hash"
10
- import Memory from "runtime/unsafe/memory"
11
- import WasmI32 from "runtime/unsafe/wasmi32"
12
- import { allocateArray, untagSimpleNumber } from "runtime/dataStructures"
9
+ module Map
10
+
11
+ from "list" include List
12
+ from "array" include Array
13
+ from "option" include Option
14
+ from "runtime/dataStructures" include DataStructures
15
+ use DataStructures.{ allocateArray, untagSimpleNumber }
16
+ from "hash" include Hash
17
+ use Hash.{ hash }
18
+ from "runtime/unsafe/memory" include Memory
19
+ from "runtime/unsafe/wasmi32" include WasmI32
13
20
 
14
21
  // TODO: Consider implementing this as List<(Box<k>, Box<v>)>
15
- record Bucket<k, v> {
22
+ record rec Bucket<k, v> {
16
23
  mut key: k,
17
24
  mut value: v,
18
25
  mut next: Option<Bucket<k, v>>,
19
26
  }
20
27
 
21
- /**
22
- * @section Types: Type declarations included in the Map module.
23
- */
24
-
25
- record Map<k, v> {
28
+ abstract record Map<k, v> {
26
29
  mut size: Number,
27
30
  mut buckets: Array<Option<Bucket<k, v>>>,
28
31
  }
29
32
 
30
33
  /**
31
- * @section Values: Functions for working with Maps.
34
+ * Represents the internal state of a map.
32
35
  */
36
+ provide record InternalMapStats {
37
+ currentSize: Number,
38
+ bucketCount: Number,
39
+ }
33
40
 
34
41
  /**
35
- * Creates a new empty map with an initial storage of the given size. As values are added or removed, the internal storage may grow or shrink. Generally, you won't need to care about the storage size of your map and can use `Map.make()` instead.
42
+ * Creates a new empty map with an initial storage of the given size. As
43
+ * values are added or removed, the internal storage may grow or shrink.
44
+ * Generally, you won't need to care about the storage size of your map and
45
+ * can use the default size.
36
46
  *
37
47
  * @param size: The initial storage size of the map
38
48
  * @returns An empty map with the given initial storage size
39
49
  *
40
50
  * @since v0.2.0
51
+ * @history v0.6.0: Merged with `makeSized`; modified signature to accept size
41
52
  */
42
- export let makeSized = size => { // TODO: This could take an `eq` function to custom comparisons
53
+ provide let make = (size=16) => { // TODO: This could take an `eq` function to custom comparisons
43
54
  let buckets = Array.make(size, None)
44
55
  { size: 0, buckets }
45
56
  }
46
57
 
47
- /**
48
- * Creates a new, empty map.
49
- *
50
- * @returns An empty map
51
- *
52
- * @since v0.2.0
53
- */
54
- export let make = () => {
55
- makeSized(16)
56
- }
57
-
58
58
  let getBucketIndex = (key, buckets) => {
59
59
  let bucketsLength = Array.length(buckets)
60
60
  let hashedKey = hash(key)
@@ -130,7 +130,7 @@ let rec replaceInBucket = (key, value, node) => {
130
130
  *
131
131
  * @since v0.2.0
132
132
  */
133
- export let set = (key, value, map) => {
133
+ provide let set = (key, value, map) => {
134
134
  let buckets = map.buckets
135
135
  let idx = getBucketIndex(key, buckets)
136
136
  let bucket = buckets[idx]
@@ -174,7 +174,7 @@ let rec valueFromBucket = (key, node) => {
174
174
  *
175
175
  * @since v0.2.0
176
176
  */
177
- export let get = (key, map) => {
177
+ provide let get = (key, map) => {
178
178
  let buckets = map.buckets
179
179
  let idx = getBucketIndex(key, buckets)
180
180
  let bucket = buckets[idx]
@@ -204,7 +204,7 @@ let rec nodeInBucket = (key, node) => {
204
204
  *
205
205
  * @since v0.2.0
206
206
  */
207
- export let contains = (key, map) => {
207
+ provide let contains = (key, map) => {
208
208
  let buckets = map.buckets
209
209
  let idx = getBucketIndex(key, buckets)
210
210
  let bucket = buckets[idx]
@@ -236,7 +236,7 @@ let rec removeInBucket = (key, node) => {
236
236
  *
237
237
  * @since v0.2.0
238
238
  */
239
- export let remove = (key, map) => {
239
+ provide let remove = (key, map) => {
240
240
  let buckets = map.buckets
241
241
  let idx = getBucketIndex(key, buckets)
242
242
  let bucket = buckets[idx]
@@ -265,7 +265,7 @@ export let remove = (key, map) => {
265
265
  *
266
266
  * @since v0.3.0
267
267
  */
268
- export let update = (key, fn, map) => {
268
+ provide let update = (key, fn, map) => {
269
269
  let val = get(key, map)
270
270
  match (fn(val)) {
271
271
  Some(next) => set(key, next, map),
@@ -281,7 +281,7 @@ export let update = (key, fn, map) => {
281
281
  *
282
282
  * @since v0.2.0
283
283
  */
284
- export let size = map => {
284
+ provide let size = map => {
285
285
  map.size
286
286
  }
287
287
 
@@ -293,7 +293,7 @@ export let size = map => {
293
293
  *
294
294
  * @since v0.2.0
295
295
  */
296
- export let isEmpty = map => {
296
+ provide let isEmpty = map => {
297
297
  size(map) == 0
298
298
  }
299
299
 
@@ -304,7 +304,7 @@ export let isEmpty = map => {
304
304
  *
305
305
  * @since v0.2.0
306
306
  */
307
- export let clear = map => {
307
+ provide let clear = map => {
308
308
  map.size = 0
309
309
  let buckets = map.buckets
310
310
  Array.forEachi((bucket, idx) => {
@@ -331,7 +331,7 @@ let rec forEachBucket = (fn, node) => {
331
331
  * @since v0.2.0
332
332
  * @history v0.5.0: Ensured the iterator function return type is always `Void`
333
333
  */
334
- export let forEach = (fn, map) => {
334
+ provide let forEach = (fn, map) => {
335
335
  let buckets = map.buckets
336
336
  Array.forEach(bucket => {
337
337
  forEachBucket(fn, bucket)
@@ -356,7 +356,7 @@ let rec reduceEachBucket = (fn, node, acc) => {
356
356
  *
357
357
  * @since v0.2.0
358
358
  */
359
- export let reduce = (fn, init, map) => {
359
+ provide let reduce = (fn, init, map) => {
360
360
  let buckets = map.buckets
361
361
  let mut acc = init
362
362
  Array.forEach(bucket => {
@@ -373,7 +373,7 @@ export let reduce = (fn, init, map) => {
373
373
  *
374
374
  * @since v0.2.0
375
375
  */
376
- export let keys = map => {
376
+ provide let keys = map => {
377
377
  reduce((list, key, _value) => [key, ...list], [], map)
378
378
  }
379
379
 
@@ -385,7 +385,7 @@ export let keys = map => {
385
385
  *
386
386
  * @since v0.2.0
387
387
  */
388
- export let values = map => {
388
+ provide let values = map => {
389
389
  reduce((list, _key, value) => [value, ...list], [], map)
390
390
  }
391
391
 
@@ -397,7 +397,7 @@ export let values = map => {
397
397
  *
398
398
  * @since v0.2.0
399
399
  */
400
- export let toList = map => {
400
+ provide let toList = map => {
401
401
  reduce((list, key, value) => [(key, value), ...list], [], map)
402
402
  }
403
403
 
@@ -409,7 +409,7 @@ export let toList = map => {
409
409
  *
410
410
  * @since v0.2.0
411
411
  */
412
- export let fromList = list => {
412
+ provide let fromList = list => {
413
413
  let map = make()
414
414
  List.forEach(pair => {
415
415
  let (key, value) = pair
@@ -427,24 +427,26 @@ export let fromList = list => {
427
427
  * @since v0.2.0
428
428
  */
429
429
  @unsafe
430
- export let toArray: Map<a, b> -> Array<(a, b)> = map => {
430
+ provide let toArray = (map: Map<a, b>) => {
431
+ use WasmI32.{ (*) }
431
432
  let length = untagSimpleNumber(map.size)
432
433
  let array = WasmI32.toGrain(allocateArray(length))
433
434
  @unsafe
434
435
  let reducer = (i, key, value) => {
436
+ use WasmI32.{ (+) as addWasmI32 }
435
437
  // Assign the values into the array.
436
438
  // We store them directly to prevent GC on uninitialized array data.
437
439
  let array = WasmI32.fromGrain(array)
438
440
  let item = (key, value)
439
441
  WasmI32.store(
440
- WasmI32.add(array, WasmI32.mul(untagSimpleNumber(i), 4n)),
442
+ addWasmI32(array, untagSimpleNumber(i) * 4n),
441
443
  Memory.incRef(WasmI32.fromGrain(item)),
442
444
  8n
443
445
  )
444
446
  i + 1
445
447
  }
446
448
  reduce(reducer, 0, map)
447
- array
449
+ array: Array<(a, b)>
448
450
  }
449
451
 
450
452
  /**
@@ -455,7 +457,7 @@ export let toArray: Map<a, b> -> Array<(a, b)> = map => {
455
457
  *
456
458
  * @since v0.2.0
457
459
  */
458
- export let fromArray = array => {
460
+ provide let fromArray = array => {
459
461
  let map = make()
460
462
  Array.forEach(pair => {
461
463
  let (key, value) = pair
@@ -472,12 +474,16 @@ export let fromArray = array => {
472
474
  *
473
475
  * @since v0.2.0
474
476
  */
475
- export let filter = (predicate, map) => {
476
- let keysToRemove = reduce((list, key, value) => if (!predicate(key, value)) {
477
- [key, ...list]
478
- } else {
479
- list
480
- }, [], map)
477
+ provide let filter = (fn, map) => {
478
+ let keysToRemove = reduce(
479
+ (list, key, value) => if (!fn(key, value)) {
480
+ [key, ...list]
481
+ } else {
482
+ list
483
+ },
484
+ [],
485
+ map
486
+ )
481
487
  List.forEach(key => {
482
488
  remove(key, map)
483
489
  }, keysToRemove)
@@ -491,11 +497,10 @@ export let filter = (predicate, map) => {
491
497
  *
492
498
  * @since v0.2.0
493
499
  */
494
- export let reject = (predicate, map) => {
495
- filter((key, value) => !predicate(key, value), map)
500
+ provide let reject = (fn, map) => {
501
+ filter((key, value) => !fn(key, value), map)
496
502
  }
497
503
 
498
- // TODO(#190): Should return a Record type instead of a Tuple
499
504
  /**
500
505
  * Provides data representing the internal state state of the map.
501
506
  *
@@ -503,7 +508,505 @@ export let reject = (predicate, map) => {
503
508
  * @returns The internal state of the map
504
509
  *
505
510
  * @since v0.2.0
511
+ * @history v0.6.0: Return `InternalMapStats` record instead of a tuple
506
512
  */
507
- export let getInternalStats = map => {
508
- (map.size, Array.length(map.buckets))
513
+ provide let getInternalStats = map => {
514
+ { currentSize: map.size, bucketCount: Array.length(map.buckets) }
515
+ }
516
+
517
+ /**
518
+ * An immutable map implementation.
519
+ *
520
+ * @since v0.6.0
521
+ * @history v0.5.4: Originally in `"immutablemap"` module
522
+ */
523
+ provide module Immutable {
524
+ // implementation based on the paper "Implementing Sets Efficiently in a
525
+ // Functional Language" by Stephen Adams
526
+ record rec Node<k, v> {
527
+ key: k,
528
+ val: v,
529
+ size: Number,
530
+ left: Map<k, v>,
531
+ right: Map<k, v>,
532
+ }
533
+ and abstract enum Map<k, v> {
534
+ Empty,
535
+ Tree(Node<k, v>),
536
+ }
537
+
538
+ // semi-arbitrary value chosen for algorithm for determining when to balance
539
+ // trees; no tree can have a left subtree containing this number of times
540
+ // more elements than its right subtree or vice versa
541
+ let weight = 4
542
+
543
+ /**
544
+ * An empty map
545
+ *
546
+ * @since v0.6.0
547
+ * @history v0.5.4: Originally in `"immutablemap"` module
548
+ */
549
+ provide let empty = Empty
550
+
551
+ // returns the key-value pair of the minimum key in a tree
552
+ let rec min = node => {
553
+ match (node) {
554
+ Tree({ key, val, left: Empty, _ }) => (key, val),
555
+ Tree({ left, _ }) => min(left),
556
+ Empty => fail "Impossible: min of empty element in Map.Immutable",
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Provides the count of key-value pairs stored within the map.
562
+ *
563
+ * @param map: The map to inspect
564
+ * @returns The count of key-value pairs in the map
565
+ *
566
+ * @since v0.6.0
567
+ * @history v0.5.4: Originally in `"immutablemap"` module
568
+ */
569
+ provide let size = map => {
570
+ match (map) {
571
+ Empty => 0,
572
+ Tree({ size, _ }) => size,
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Determines if the map contains no key-value pairs.
578
+ *
579
+ * @param map: The map to inspect
580
+ * @returns `true` if the given map is empty or `false` otherwise
581
+ *
582
+ * @since v0.6.0
583
+ * @history v0.5.4: Originally in `"immutablemap"` module
584
+ */
585
+ provide let isEmpty = map => {
586
+ match (map) {
587
+ Empty => true,
588
+ Tree(_) => false,
589
+ }
590
+ }
591
+
592
+ let unwrapTree = node => {
593
+ match (node) {
594
+ Empty =>
595
+ fail "Impossible: Map.Immutable unwrapTree got an empty tree node",
596
+ Tree(tree) => tree,
597
+ }
598
+ }
599
+
600
+ // helper function for creating a tree node with correct size from
601
+ // two balanced trees
602
+ let makeNode = (key, val, left, right) => {
603
+ Tree({ key, val, size: 1 + size(left) + size(right), left, right })
604
+ }
605
+
606
+ // note: see Figure 1 of paper referenced above for visual illustration of
607
+ // the rotations below
608
+
609
+ // node rotation moving the left subtree of the right node to the left side
610
+ let singleL = (key, val, left, right) => {
611
+ let { key: rKey, val: rVal, left: rl, right: rr, _ } = unwrapTree(right)
612
+ makeNode(rKey, rVal, makeNode(key, val, left, rl), rr)
613
+ }
614
+
615
+ // node rotation moving left child of right tree to the root
616
+ let doubleL = (key, val, left, right) => {
617
+ let { key: rKey, val: rVal, left: rl, right: rr, _ } = unwrapTree(right)
618
+ let { key: rlKey, val: rlVal, left: rll, right: rlr, _ } = unwrapTree(rl)
619
+ makeNode(
620
+ rlKey,
621
+ rlVal,
622
+ makeNode(key, val, left, rll),
623
+ makeNode(rKey, rVal, rlr, rr)
624
+ )
625
+ }
626
+
627
+ // node rotation moving the right subtree of the left node to the right side
628
+ let singleR = (key, val, left, right) => {
629
+ let { key: lKey, val: lVal, left: ll, right: lr, _ } = unwrapTree(left)
630
+ makeNode(lKey, lVal, ll, makeNode(key, val, lr, right))
631
+ }
632
+
633
+ // node rotation moving right child of left tree to the root
634
+ let doubleR = (key, val, left, right) => {
635
+ let { key: lKey, val: lVal, left: ll, right: lr, _ } = unwrapTree(left)
636
+ let { key: lrKey, val: lrVal, left: lrl, right: lrr, _ } = unwrapTree(lr)
637
+ makeNode(
638
+ lrKey,
639
+ lrVal,
640
+ makeNode(lKey, lVal, ll, lrl),
641
+ makeNode(key, val, lrr, right)
642
+ )
643
+ }
644
+
645
+ // creates a new node after either the left or right trees have just had an
646
+ // element inserted or removed from them, maintaining balance in the tree
647
+ let balancedNode = (key, val, left, right) => {
648
+ let makeNodeFn = if (size(left) + size(right) < 2) {
649
+ makeNode
650
+ } else if (size(right) > weight * size(left)) {
651
+ // if the right tree is too much larger than the left then move part of
652
+ // the right tree to the left side
653
+ let { left: rl, right: rr, _ } = unwrapTree(right)
654
+ if (size(rl) < size(rr)) singleL else doubleL
655
+ } else if (size(left) > weight * size(right)) {
656
+ // if the left tree is too much larger than the right then move part of
657
+ // the left tree to the right side
658
+ let { left: ll, right: lr, _ } = unwrapTree(left)
659
+ if (size(lr) < size(ll)) singleR else doubleR
660
+ } else {
661
+ // if neither tree is too much larger than the other then simply create
662
+ // a new node
663
+ makeNode
664
+ }
665
+
666
+ makeNodeFn(key, val, left, right)
667
+ }
668
+
669
+ /**
670
+ * Produces a new map containing a new key-value pair. If the key already exists in the map, the value is replaced.
671
+ *
672
+ * @param key: The unique key in the map
673
+ * @param value: The value to store
674
+ * @param map: The base map
675
+ * @returns A new map containing the new key-value pair
676
+ *
677
+ * @since v0.6.0
678
+ * @history v0.5.4: Originally in `"immutablemap"` module
679
+ */
680
+ provide let rec set = (key, value, map) => {
681
+ match (map) {
682
+ Empty => Tree({ key, val: value, size: 1, left: Empty, right: Empty }),
683
+ Tree({ key: nodeKey, val: nodeVal, left, right, _ }) => {
684
+ match (compare(key, nodeKey)) {
685
+ cmp when cmp < 0 =>
686
+ balancedNode(nodeKey, nodeVal, set(key, value, left), right),
687
+ cmp when cmp > 0 =>
688
+ balancedNode(nodeKey, nodeVal, left, set(key, value, right)),
689
+ _ => makeNode(key, value, left, right),
690
+ }
691
+ },
692
+ }
693
+ }
694
+
695
+ /**
696
+ * Retrieves the value for the given key.
697
+ *
698
+ * @param key: The key to access
699
+ * @param map: The map to access
700
+ * @returns `Some(value)` if the key exists in the map or `None` otherwise
701
+ *
702
+ * @since v0.6.0
703
+ * @history v0.5.4: Originally in `"immutablemap"` module
704
+ */
705
+ provide let rec get = (key, map) => {
706
+ match (map) {
707
+ Empty => None,
708
+ Tree({ key: nodeKey, val: nodeVal, left, right, _ }) => {
709
+ match (compare(key, nodeKey)) {
710
+ cmp when cmp < 0 => get(key, left),
711
+ cmp when cmp > 0 => get(key, right),
712
+ _ => Some(nodeVal),
713
+ }
714
+ },
715
+ }
716
+ }
717
+
718
+ /**
719
+ * Determines if the map contains the given key. In such a case, it will always contain a value for the given key.
720
+ *
721
+ * @param key: The key to search for
722
+ * @param map: The map to search
723
+ * @returns `true` if the map contains the given key or `false` otherwise
724
+ *
725
+ * @since v0.6.0
726
+ * @history v0.5.4: Originally in `"immutablemap"` module
727
+ */
728
+ provide let rec contains = (key, map) => {
729
+ Option.isSome(get(key, map))
730
+ }
731
+
732
+ // removes the minimum element from a tree
733
+ let rec removeMin = node => {
734
+ match (node) {
735
+ Tree({ left: Empty, right, _ }) => right,
736
+ Tree({ key, val, left, right, _ }) =>
737
+ balancedNode(key, val, removeMin(left), right),
738
+ _ => fail "Impossible: Map.Immutable removeMin on empty node",
739
+ }
740
+ }
741
+
742
+ // helper function for removing a node by creating a new node containing the
743
+ // removed node's left and right subtrees
744
+ let removeInner = (left, right) => {
745
+ match ((left, right)) {
746
+ (Empty, node) | (node, Empty) => node,
747
+ (left, right) => {
748
+ let (minKey, minVal) = min(right)
749
+ balancedNode(minKey, minVal, left, removeMin(right))
750
+ },
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Produces a new map without the key-value pair corresponding to the given
756
+ * key. If the key doesn't exist in the map, the map will be returned unmodified.
757
+ *
758
+ * @param key: The key to exclude
759
+ * @param map: The map to exclude from
760
+ * @returns A new map without the given key
761
+ *
762
+ * @since v0.6.0
763
+ * @history v0.5.4: Originally in `"immutablemap"` module
764
+ */
765
+ provide let rec remove = (key, map) => {
766
+ match (map) {
767
+ Empty => Empty,
768
+ Tree({ key: nodeKey, val: nodeVal, left, right, _ }) => {
769
+ match (compare(key, nodeKey)) {
770
+ cmp when cmp < 0 =>
771
+ balancedNode(nodeKey, nodeVal, remove(key, left), right),
772
+ cmp when cmp > 0 =>
773
+ balancedNode(nodeKey, nodeVal, left, remove(key, right)),
774
+ _ => removeInner(left, right),
775
+ }
776
+ },
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Produces a new map by calling an updater function that receives the
782
+ * previously stored value as an `Option` and returns the new value to be
783
+ * stored as an `Option`. If the key didn't exist previously, the value
784
+ * will be `None`. If `None` is returned from the updater function, the
785
+ * key-value pair is excluded.
786
+ *
787
+ * @param key: The unique key in the map
788
+ * @param fn: The updater function
789
+ * @param map: The base map
790
+ * @returns A new map with the value at the given key modified according to the function's output
791
+ *
792
+ * @since v0.6.0
793
+ * @history v0.5.4: Originally in `"immutablemap"` module
794
+ */
795
+ provide let update = (key, fn, map) => {
796
+ let val = get(key, map)
797
+ match (fn(val)) {
798
+ Some(next) => set(key, next, map),
799
+ None => remove(key, map),
800
+ }
801
+ }
802
+
803
+ /**
804
+ * Iterates the map, calling an iterator function with each key and value.
805
+ *
806
+ * @param fn: The iterator function to call with each key and value
807
+ * @param map: The map to iterate
808
+ *
809
+ * @since v0.6.0
810
+ * @history v0.5.4: Originally in `"immutablemap"` module
811
+ */
812
+ provide let forEach = (fn, map) => {
813
+ let rec forEachInner = node => {
814
+ match (node) {
815
+ Empty => void,
816
+ Tree({ key, val, left, right, _ }) => {
817
+ forEachInner(left)
818
+ fn(key, val): Void
819
+ forEachInner(right)
820
+ },
821
+ }
822
+ }
823
+ forEachInner(map)
824
+ }
825
+
826
+ /**
827
+ * Combines all key-value pairs of a map using a reducer function.
828
+ *
829
+ * @param fn: The reducer function to call on each key and value, where the value returned will be the next accumulator value
830
+ * @param init: The initial value to use for the accumulator on the first iteration
831
+ * @param map: The map to iterate
832
+ * @returns The final accumulator returned from `fn`
833
+ *
834
+ * @since v0.6.0
835
+ * @history v0.5.4: Originally in `"immutablemap"` module
836
+ */
837
+ provide let reduce = (fn, init, map) => {
838
+ let rec reduceInner = (acc, node) => {
839
+ match (node) {
840
+ Empty => acc,
841
+ Tree({ key, val, left, right, _ }) => {
842
+ let newAcc = fn(reduceInner(acc, left), key, val)
843
+ reduceInner(newAcc, right)
844
+ },
845
+ }
846
+ }
847
+ reduceInner(init, map)
848
+ }
849
+
850
+ // joins two trees with a value, preserving the BST property of left children
851
+ // being less the node and right children being greater than the node
852
+ let rec concat3 = (key, val, left, right) => {
853
+ match ((left, right)) {
854
+ (Empty, node) | (node, Empty) => set(key, val, node),
855
+ (Tree(left) as leftOpt, Tree(right) as rightOpt) => {
856
+ let { size: lSize, key: lKey, left: lLeft, right: lRight, val: lVal } =
857
+ left
858
+ let { size: rSize, key: rKey, left: rLeft, right: rRight, val: rVal } =
859
+ right
860
+ if (weight * lSize < rSize) {
861
+ balancedNode(rKey, rVal, concat3(key, val, leftOpt, rLeft), rRight)
862
+ } else if (weight * rSize < lSize) {
863
+ balancedNode(lKey, lVal, lLeft, concat3(key, val, lRight, rightOpt))
864
+ } else {
865
+ makeNode(key, val, leftOpt, rightOpt)
866
+ }
867
+ },
868
+ }
869
+ }
870
+
871
+ // concatenates two trees of arbitrary size
872
+ let concat = (node1, node2) => {
873
+ match (node2) {
874
+ Empty => node1,
875
+ _ => {
876
+ let (minKey, minVal) = min(node2)
877
+ concat3(minKey, minVal, node1, removeMin(node2))
878
+ },
879
+ }
880
+ }
881
+
882
+ let reduceRight = (fn, init, map) => {
883
+ let rec reduceInner = (acc, node) => {
884
+ match (node) {
885
+ Empty => acc,
886
+ Tree({ key, val, left, right, _ }) => {
887
+ let newAcc = fn(reduceInner(acc, right), key, val)
888
+ reduceInner(newAcc, left)
889
+ },
890
+ }
891
+ }
892
+ reduceInner(init, map)
893
+ }
894
+
895
+ /**
896
+ * Enumerates all keys in the given map.
897
+ *
898
+ * @param map: The map to enumerate
899
+ * @returns A list containing all keys from the given map
900
+ *
901
+ * @since v0.6.0
902
+ * @history v0.5.4: Originally in `"immutablemap"` module
903
+ */
904
+ provide let keys = map => {
905
+ reduceRight((list, key, _) => [key, ...list], [], map)
906
+ }
907
+
908
+ /**
909
+ * Enumerates all values in the given map.
910
+ *
911
+ * @param map: The map to enumerate
912
+ * @returns A list containing all values from the given map
913
+ *
914
+ * @since v0.6.0
915
+ * @history v0.5.4: Originally in `"immutablemap"` module
916
+ */
917
+ provide let values = map => {
918
+ reduceRight((list, _, value) => [value, ...list], [], map)
919
+ }
920
+
921
+ /**
922
+ * Produces a new map excluding the key-value pairs where a predicate function returns `false`.
923
+ *
924
+ * @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
925
+ * @param map: The map to iterate
926
+ * @returns A new map excluding the key-value pairs not fulfilling the predicate
927
+ *
928
+ * @since v0.6.0
929
+ * @history v0.5.4: Originally in `"immutablemap"` module
930
+ */
931
+ provide let filter = (fn, map) => {
932
+ let rec filterInner = node => {
933
+ match (node) {
934
+ Empty => Empty,
935
+ Tree({ key, val, left, right, _ }) => {
936
+ if (fn(key, val)) {
937
+ concat3(key, val, filterInner(left), filterInner(right))
938
+ } else {
939
+ concat(filterInner(left), filterInner(right))
940
+ }
941
+ },
942
+ }
943
+ }
944
+ filterInner(map)
945
+ }
946
+
947
+ /**
948
+ * Produces a new map excluding the key-value pairs where a predicate function returns `true`.
949
+ *
950
+ * @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
951
+ * @param map: The map to iterate
952
+ * @returns A new map excluding the key-value pairs fulfilling the predicate
953
+ *
954
+ * @since v0.6.0
955
+ * @history v0.5.4: Originally in `"immutablemap"` module
956
+ */
957
+ provide let reject = (fn, map) => {
958
+ filter((key, val) => !fn(key, val), map)
959
+ }
960
+
961
+ /**
962
+ * Creates a map from a list.
963
+ *
964
+ * @param list: The list to convert
965
+ * @returns A map containing all key-value pairs from the list
966
+ *
967
+ * @since v0.6.0
968
+ * @history v0.5.4: Originally in `"immutablemap"` module
969
+ */
970
+ provide let fromList = list => {
971
+ List.reduce((map, (key, val)) => set(key, val, map), empty, list)
972
+ }
973
+
974
+ /**
975
+ * Enumerates all key-value pairs in the given map.
976
+ *
977
+ * @param map: The map to enumerate
978
+ * @returns A list containing all key-value pairs from the given map
979
+ *
980
+ * @since v0.6.0
981
+ * @history v0.5.4: Originally in `"immutablemap"` module
982
+ */
983
+ provide let toList = map => {
984
+ reduceRight((list, key, val) => [(key, val), ...list], [], map)
985
+ }
986
+
987
+ /**
988
+ * Creates a map from an array.
989
+ *
990
+ * @param array: The array to convert
991
+ * @returns A map containing all key-value pairs from the array
992
+ *
993
+ * @since v0.6.0
994
+ * @history v0.5.4: Originally in `"immutablemap"` module
995
+ */
996
+ provide let fromArray = array => {
997
+ Array.reduce((map, (key, val)) => set(key, val, map), empty, array)
998
+ }
999
+
1000
+ /**
1001
+ * Converts a map into an array of its key-value pairs.
1002
+ *
1003
+ * @param map: The map to convert
1004
+ * @returns An array containing all key-value pairs from the given map
1005
+ *
1006
+ * @since v0.6.0
1007
+ * @history v0.5.4: Originally in `"immutablemap"` module
1008
+ */
1009
+ provide let toArray = map => {
1010
+ Array.fromList(toList(map))
1011
+ }
509
1012
  }