refactor: use contextual-dot throughout#489
Conversation
Use map.method(...) with implicit Nat.compare and value equality where applicable, matching the style of the updated empty-map suite. Made-with: Cursor
Replace module calls with receiver syntax so key comparison is implicit. Made-with: Cursor
Use map.equal(...) for singleton and small-map equality checks where Nat.compare and Text.equal are inferred. Made-with: Cursor
Use receiver syntax for buildTestMap/smallMap helpers, compare/delete/swap tests, and maxEntry/minEntry iteration. Made-with: Cursor
Replace imperative Set module calls with receiver syntax; use implicit Nat.compare for add/remove/insert/delete, set relations, bulk ops, and valuesFrom/reverseValuesFrom. Made-with: Cursor
Use pure set receiver calls and field .size; simplify join/flatten and compare pipelines. Made-with: Cursor
Use pq.push/pop/values with implicit Nat.compare for ordering. Made-with: Cursor
Replace Queue.compare(q1, q2, Nat.compare) with q1.compare(q2). Made-with: Cursor
Call methods on var-array receivers with implicit compare. Made-with: Cursor
Call methods on array receivers with implicit compare. Made-with: Cursor
Use vec.binarySearch(...) with implicit compare. Made-with: Cursor
Add fast-check for property tests; use let where variables are not reassigned and align record field terminators in List. Made-with: Cursor
Benchmark Resultsbench/ArrayBuilding.bench.mo
|
| 1000 | 100000 | 1000000 | |
|---|---|---|---|
| List | 542_175 |
47_994_641 |
475_069_688 |
| Buffer | 342_033 |
33_903_445 |
339_003_651 |
| pure/List | 302_163 |
30_003_600 |
300_055_973 |
| VarArray ?T | 180_546 |
17_802_958 |
178_003_164 |
| VarArray T | 160_841 |
15_803_253 |
158_003_459 |
| Array (baseline) | 42_715 |
4_003_127 |
40_003_333 |
Heap
| 1000 | 100000 | 1000000 | |
|---|---|---|---|
| List | 272 B |
272 B |
272 B |
| Buffer | 272 B |
272 B |
272 B |
| pure/List | 272 B |
272 B |
272 B |
| VarArray ?T | 272 B |
272 B |
272 B |
| VarArray T | 272 B |
272 B |
272 B |
| Array (baseline) | 272 B |
272 B |
272 B |
Garbage Collection
| 1000 | 100000 | 1000000 | |
|---|---|---|---|
| List | 9.96 KiB |
797.46 KiB |
7.67 MiB |
| Buffer | 8.71 KiB |
782.15 KiB |
7.63 MiB |
| pure/List | 19.95 KiB |
1.91 MiB |
19.07 MiB |
| VarArray ?T | 8.24 KiB |
781.68 KiB |
7.63 MiB |
| VarArray T | 8.23 KiB |
781.67 KiB |
7.63 MiB |
| Array (baseline) | 4.3 KiB |
391.02 KiB |
3.82 MiB |
bench/Base64.bench.mo $({\color{gray}0\%})$
Base64
Compare zero bytes vs mixed bytes encoding to Base64
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| zero bytes | mixed bytes | |
|---|---|---|
| 1 | 948 | 948 |
| 10 | 3_242 | 3_242 |
| 100 | 22_742 | 22_742 |
| 1000 | 217_742 | 217_742 |
| 10000 | 2_168_137 | 2_167_783 |
Heap
| zero bytes | mixed bytes | |
|---|---|---|
| 1 | 272 B | 272 B |
| 10 | 272 B | 272 B |
| 100 | 272 B | 272 B |
| 1000 | 272 B | 272 B |
| 10000 | 272 B | 272 B |
Garbage Collection
| zero bytes | mixed bytes | |
|---|---|---|
| 1 | 344 B | 344 B |
| 10 | 496 B | 496 B |
| 100 | 1.77 KiB | 1.77 KiB |
| 1000 | 14.66 KiB | 14.66 KiB |
| 10000 | 143.57 KiB | 143.57 KiB |
bench/FromIters.bench.mo $({\color{red}+0.05\%})$
Benchmarking the fromIter functions
Columns describe the number of elements in the input iter.
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| 100 | 10_000 | 100_000 | |
|---|---|---|---|
| Array.fromIter | 48_828 |
4_712_089 |
47_103_197 |
| List.fromIter | 31_762 |
3_061_605 |
30_603_615 |
| List.fromIter . Iter.reverse | 50_359 |
4_832_625 |
48_305_537 |
Heap
| 100 | 10_000 | 100_000 | |
|---|---|---|---|
| Array.fromIter | 272 B |
272 B |
272 B |
| List.fromIter | 272 B |
272 B |
272 B |
| List.fromIter . Iter.reverse | 272 B |
272 B |
272 B |
Garbage Collection
| 100 | 10_000 | 100_000 | |
|---|---|---|---|
| Array.fromIter | 2.76 KiB |
234.79 KiB |
2.29 MiB |
| List.fromIter | 3.51 KiB |
312.88 KiB |
3.05 MiB |
| List.fromIter . Iter.reverse | 5.11 KiB |
469.17 KiB |
4.58 MiB |
bench/ListBufferNewArray.bench.mo $({\color{green}-4.07\%})$
List vs. Buffer for creating known-size arrays
Performance comparison between List and Buffer for creating a new array.
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| 0 (baseline) | 1 | 5 | 10 | 100 (for loop) | |
|---|---|---|---|---|---|
| List | 1_431 |
2_751 |
8_731 |
13_524 |
73_224 |
| pure/List | 1_311 |
1_419 |
2_503 |
3_863 |
31_928 |
| Buffer | 2_183 |
2_335 |
3_582 |
5_147 |
36_700 |
Heap
| 0 (baseline) | 1 | 5 | 10 | 100 (for loop) | |
|---|---|---|---|---|---|
| List | 272 B |
272 B |
272 B |
272 B |
272 B |
| pure/List | 272 B |
272 B |
272 B |
272 B |
272 B |
| Buffer | 272 B |
272 B |
272 B |
272 B |
272 B |
Garbage Collection
| 0 (baseline) | 1 | 5 | 10 | 100 (for loop) | |
|---|---|---|---|---|---|
| List | 476 B |
516 B |
676 B |
784 B |
1.84 KiB |
| pure/List | 360 B |
380 B |
460 B |
560 B |
2.3 KiB |
| Buffer | 856 B |
864 B |
896 B |
936 B |
1.62 KiB |
bench/PriorityQueues.bench.mo $({\color{green}-2.68\%})$
Different priority queue implementations
_Compare the performance of the following priority queue implementations:
-
PriorityQueue: Binary heap implementation overList. -
PriorityQueueSet: Wrapper overSet<(T, Nat)>._
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| A) PriorityQueue | B) PriorityQueueSet | |
|---|---|---|
| 1.) 100000 operations (push:pop = 1:1) | 568_913_162 |
522_729_679 |
| 2.) 100000 operations (push:pop = 2:1) | 707_495_283 |
809_692_987 |
| 3.) 100000 operations (push:pop = 10:1) | 336_409_806 |
873_180_969 |
| 4.) 100000 operations (only push) | 176_983_018 |
886_824_651 |
| 5.) 50000 pushes, then 50000 pops | 745_226_761 |
961_778_074 |
| 6.) 50000 pushes, then 25000 "pop;push"es | 504_254_251 |
922_136_355 |
Heap
| A) PriorityQueue | B) PriorityQueueSet | |
|---|---|---|
| 1.) 100000 operations (push:pop = 1:1) | 272 B |
272 B |
| 2.) 100000 operations (push:pop = 2:1) | 272 B |
272 B |
| 3.) 100000 operations (push:pop = 10:1) | 272 B |
272 B |
| 4.) 100000 operations (only push) | 272 B |
272 B |
| 5.) 50000 pushes, then 50000 pops | 272 B |
272 B |
| 6.) 50000 pushes, then 25000 "pop;push"es | 272 B |
272 B |
Garbage Collection
| A) PriorityQueue | B) PriorityQueueSet | |
|---|---|---|
| 1.) 100000 operations (push:pop = 1:1) | 15.07 MiB |
17.43 MiB |
| 2.) 100000 operations (push:pop = 2:1) | 19.73 MiB |
19.32 MiB |
| 3.) 100000 operations (push:pop = 10:1) | 8.67 MiB |
12.64 MiB |
| 4.) 100000 operations (only push) | 3.87 MiB |
9.96 MiB |
| 5.) 50000 pushes, then 50000 pops | 22.03 MiB |
26.2 MiB |
| 6.) 50000 pushes, then 25000 "pop;push"es | 14.22 MiB |
18.44 MiB |
bench/PureListStackSafety.bench.mo $({\color{red}+0.00\%})$
List Stack safety
Check stack-safety of the following pure/List-related functions.
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| pure/List.split | 24_602_588 |
| pure/List.all | 7_901_078 |
| pure/List.any | 8_001_454 |
| pure/List.map | 23_103_831 |
| pure/List.filter | 21_104_252 |
| pure/List.filterMap | 27_404_804 |
| pure/List.partition | 21_305_058 |
| pure/List.join | 33_105_390 |
| pure/List.flatten | 24_805_731 |
| pure/List.take | 24_605_728 |
| pure/List.drop | 9_904_183 |
| pure/List.foldRight | 19_105_832 |
| pure/List.merge | 31_808_648 |
| pure/List.chunks | 51_510_408 |
| pure/Queue | 142_662_569 |
Heap
| pure/List.split | 272 B |
| pure/List.all | 272 B |
| pure/List.any | 272 B |
| pure/List.map | 272 B |
| pure/List.filter | 272 B |
| pure/List.filterMap | 272 B |
| pure/List.partition | 272 B |
| pure/List.join | 272 B |
| pure/List.flatten | 272 B |
| pure/List.take | 272 B |
| pure/List.drop | 272 B |
| pure/List.foldRight | 272 B |
| pure/List.merge | 272 B |
| pure/List.chunks | 272 B |
| pure/Queue | 272 B |
Garbage Collection
| pure/List.split | 3.05 MiB |
| pure/List.all | 328 B |
| pure/List.any | 328 B |
| pure/List.map | 3.05 MiB |
| pure/List.filter | 3.05 MiB |
| pure/List.filterMap | 3.05 MiB |
| pure/List.partition | 3.05 MiB |
| pure/List.join | 3.05 MiB |
| pure/List.flatten | 3.05 MiB |
| pure/List.take | 3.05 MiB |
| pure/List.drop | 328 B |
| pure/List.foldRight | 1.53 MiB |
| pure/List.merge | 4.58 MiB |
| pure/List.chunks | 7.63 MiB |
| pure/Queue | 18.31 MiB |
bench/Queues.bench.mo $({\color{red}+0.87\%})$
Different queue implementations
Compare the performance of the following queue implementations:
-
pure/Queue: The default immutable double-ended queue implementation.- Pros: Good amortized performance, meaning that the average cost of operations is low
O(1). - Cons: In worst case, an operation can take
O(size)time rebuilding the queue as demonstrated in thePop front 2 elementsscenario.
- Pros: Good amortized performance, meaning that the average cost of operations is low
-
pure/RealTimeQueue- Pros: Every operation is guaranteed to take at most
O(1)time and space. - Cons: Poor amortized performance: Instruction cost is on average 3x for pop and 8x for push compared to
pure/Queue.
- Pros: Every operation is guaranteed to take at most
- mutable
Queue- Pros: Also
O(1)guarantees with a lower constant factor thanpure/RealTimeQueue. Amortized performance is comparable topure/Queue. - Cons: It is mutable and cannot be used in
sharedtypes (not shareable).
- Pros: Also
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| pure/Queue | pure/RealTimeQueue | mutable Queue | |
|---|---|---|---|
| Initialize with 2 elements | 3_156 |
2_368 |
3_104 |
| Push 500 elements | 90_777 |
744_283 |
219_389 |
| Pop front 2 elements | 87_030 |
4_510 |
3_911 |
| Pop 150 front&back | 92_159 |
304_972 |
124_645 |
Heap
| pure/Queue | pure/RealTimeQueue | mutable Queue | |
|---|---|---|---|
| Initialize with 2 elements | 324 B |
300 B |
352 B |
| Push 500 elements | 8.08 KiB |
8.17 KiB |
19.8 KiB |
| Pop front 2 elements | 240 B |
240 B |
192 B |
| Pop 150 front&back | -4.42 KiB |
-492 B |
-11.45 KiB |
Garbage Collection
| pure/Queue | pure/RealTimeQueue | mutable Queue | |
|---|---|---|---|
| Initialize with 2 elements | 508 B |
444 B |
456 B |
| Push 500 elements | 10.1 KiB |
137.84 KiB |
344 B |
| Pop front 2 elements | 12.19 KiB |
528 B |
424 B |
| Pop 150 front&back | 15.61 KiB |
49.66 KiB |
12.1 KiB |
bench/Sort.bench.mo $({\color{gray}0\%})$
Sort
VarArray.sortInPlace profiling
Instructions:
Heap:
Stable Memory:
Garbage Collection:
Instructions
| 100 | 1000 | 10000 | 12000 | 100000 | 1000000 | |
|---|---|---|---|---|---|---|
| old-sort | 205_485 | 2_681_849 | 35_810_354 | 43_067_857 | 442_387_583 | 5_046_582_633 |
| new-sort | 72_494 | 1_123_252 | 16_084_259 | 19_445_631 | 201_432_959 | 2_423_397_137 |
Heap
| 100 | 1000 | 10000 | 12000 | 100000 | 1000000 | |
|---|---|---|---|---|---|---|
| old-sort | 272 B | 272 B | 272 B | 272 B | 272 B | 308 B |
| new-sort | 272 B | 272 B | 272 B | 272 B | 272 B | 308 B |
Garbage Collection
| 100 | 1000 | 10000 | 12000 | 100000 | 1000000 | |
|---|---|---|---|---|---|---|
| old-sort | 736 B | 4.23 KiB | 39.39 KiB | 47.2 KiB | 390.95 KiB | 3.82 MiB |
| new-sort | 536 B | 2.28 KiB | 19.86 KiB | 23.77 KiB | 195.64 KiB | 1.91 MiB |
Note: Renamed benchmarks cannot be compared. Refer to the current baseline for manual comparison.
Collect iterators via receiver syntax; align Stack.toArray and Iter trap text. Made-with: Cursor
…ators Replace Map/PureMap module iterator calls with receiver syntax; unwrap (receiver.method()).toArray() where applicable; update tests and List.
Replace module calls like Set.values(x), Queue.values(x), PureQueue.values(x), and Stack.values(x) with x.values() (and reverseValues where applicable). Update internal pure/mutable implementations to call self.values() and other.values(). Fix accidental matches where Queue.values overlapped pureQueue.values() and initialSet.values() so receivers stay correct. PriorityQueue.values(pq, compare) is unchanged (two-argument API).
Reformat filter tests and List record fields after running the project formatter.
Bump the motoko npm package to 4.x so doc snippets compile with contextual dot and receiver-style iterators (3.x reported missing field values on Stack). Use other.entries() and other.values() where bare entries/values were left after migration; use set.values() and subSet.values() in Set join/flatten. Fix documentation typos (flatSet, textQueue) and use array .vals() in examples where .values() confused the older compiler. Changelog: ## Next (#489).
Mechanical rename across src/, test/, and bench/ Motoko sources.
Resolved conflicts in Changelog.md (keep Next note + main 2.4.0–2.3.0 sections), package.json (keep motoko ^4.4.0, ic-mops ^2.7.0 from main), and regenerated package-lock.json via npm install.
|
Hold off on reviewing, I'll do a proper review myself first. |
|
Maybe ask Cursor to enable the context dot warning as error, we can add that to the mops.toml |
Migrates tests and related tooling to contextual receiver syntax with implicit compare/equality where applicable.
Made with Cursor