-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
1664 lines (1487 loc) · 102 KB
/
index.html
File metadata and controls
1664 lines (1487 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Map.prototype.upsert Tutorial</title>
<link rel="stylesheet" href="style.css"> <!-- Link to CSS file for styling -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
</head>
<body>
<div class="container">
<!-- Sidebar for Table of Contents -->
<aside class="sidebar">
<h2>
Table of Contents
<a href="#" class="house-icon">🏠</a>
</h2>
<ul>
<li><a href="#ecmascript">ECMAScript®</a></li>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#whats-covered">What’s Covered in This Tutorial?</a></li>
<li><a href="#proposal-overview">The Map.prototype.upsert Proposal</a></li>
<li><a href="#installation">Installing Mozilla Unified</a></li>
<li><a href="#specification">How to Read the ECMA-262 Language Specification</a></li>
<li><a href="#searchfox">Using Searchfox</a></li>
<li><a href="#implementation">Implementation</a></li>
<li><a href="#issues">Issues with the Original Proposal</a></li>
<li><a href="#new-proposal">Explaining the New Proposal</a></li>
<li><a href="#implementing-new-proposal">Implementing the New Proposal</a></li>
<li><a href="#ecmarkup">Writing the Specification in Ecmarkup</a></li>
<li><a href="#optimization">Optimization</a></li>
<li><a href="#testing">Testing with Test262</a></li>
</ul>
</aside>
<!-- Main Content Area -->
<div class="content">
<header>
<h1>Map.prototype.upsert Tutorial</h1>
<p>Welcome to this detailed tutorial on how to implement and understand the Map.prototype.upsert proposal in JavaScript™.</p>
<a href="initial-emplace-spec/Map.prototype.emplace.html">Here is the original specification!</a>
<a href="key-value-callback-spec/Map.prototype.getOrInsert.html">Here is the new specification!</a>
</header>
<main>
<!-- ECMAScript® Section (non-collapsible) -->
<section id="ecmascript">
<h2>ECMAScript®</h2>
<p>
JavaScript™ is standardized by ECMAScript® and specified in the
<a href="https://ecma-international.org/publications-and-standards/standards/ecma-262/" target="_blank">
ECMA-262 language specification
</a>,
which is maintained by Ecma International through the
<a href="https://tc39.es/" target="_blank">TC39 committee</a>.
ECMAScript® defines the core features of the language, providing a standard that ensures consistency across different JavaScript™ engines.
Major engines like
<a href="https://v8.dev/" target="_blank">V8</a> (used in Chrome and Node.js),
<a href="https://developer.apple.com/documentation/javascriptcore" target="_blank">JavaScriptCore</a> (Safari),
and
<a href="https://spidermonkey.dev/" target="_blank">SpiderMonkey</a> (Firefox)
implement these specifications, allowing developers to write code that behaves similarly across different environments.
</p>
<p>
SpiderMonkey, the engine developed by Mozilla, powers JavaScript™ execution in Firefox and supports the development of new language features.
This tutorial focuses on working within SpiderMonkey to implement and test a new JavaScript™ proposal, providing insight into both the ECMAScript®
standardization process and the inner workings of a JavaScript™ engine.
</p>
</section>
<!-- Introduction Section (non-collapsible) -->
<section id="introduction">
<h2>Introduction</h2>
<p>Welcome to this detailed tutorial on how to implement and understand the Map.prototype.upsert proposal. This guide is tailored to help both beginners and advanced developers learn how to contribute to JavaScript™ language development by implementing a new feature in SpiderMonkey, Mozilla's JavaScript™ engine. We’ll cover all the necessary steps, from downloading and setting up the development environment to writing the upsert method and testing it with the official test suite, Test262.</p>
<p>We'll start with an introduction to the Map.prototype.upsert proposal, highlighting its benefits for developers. From there, you'll be guided through setting up the development environment using Mozilla's SpiderMonkey JavaScript™ engine. You'll then implement the upsert method using both self-hosted JavaScript™ and (a little bit of) C++, ensuring alignment with the ECMAScript® specification.</p>
<p>By the end of this tutorial, you'll have implemented a fully functional upsert method and gained valuable insight into the process of designing, testing, and standardizing JavaScript™ features.</p>
</section>
<!-- What's Covered Section (non-collapsible) -->
<section id="whats-covered">
<h2>What’s Covered in This Tutorial?</h2>
<ul>
<li>The Map.prototype.upsert Proposal: Learn what this proposal is, how it works, and why it’s beneficial for JavaScript™ developers.</li>
<li>Setting Up the Development Environment: How to download and build Mozilla Unified, the repository that contains SpiderMonkey.</li>
<li>Implementing the Proposal: Implementing the upsert method in self-hosted JavaScript™ and C++.</li>
<li>Debugging and Testing: How to test your implementation using Test262 and run custom scripts.</li>
<li>Optimizing Your Code: Learn about performance considerations and optimizations.</li>
<li>Contributing to the ECMAScript® Standard: Understand how to write specification-compliant code and contribute to ECMAScript®.</li>
</ul>
</section>
<!-- Collapsible Sections for Detailed Topics -->
<section class="collapsible" id="proposal-overview">
<h2>The Map.prototype.upsert Proposal</h2>
<div class="content-body">
<p><strong>What is it?</strong></p>
<p> <code>Map.prototype.upsert</code> is a new method for JavaScript™'s <code>Map</code>-object. The operation simplifies the process of inserting or updating <code>key-value</code> pairs in the Map. The function simply checks for existence of a key to either <code>insert</code> or <code>update</code> new <code>key-value</code> pairs.</p>
<p> <strong>How does it work?</strong>
The <code>upsert</code> operation takes two arguments: a <code>key</code> and a <code>handler</code> object. The <code>handler</code> contains two properties:</p>
<ul>
<li><p><code>update</code>: Function to modify <code>value</code> of a <code>key</code> if already present in the <code>Map</code>.</p>
</li>
<li><p><code>insert</code>: Function that generates a <code>default-value</code> to be set to the belonging <code>value</code> of the checked <code>key</code>.</p>
<p> <strong>The function follow these steps:</strong></p>
<ol>
<li>The <code>Map</code> is checked for the <code>key</code> passed as argument. If found:<ul>
<li>It checks the <code>handler</code> for an <code>update</code> function. If found, this is used to <code>update</code> the <code>value</code> belonging to the passed <code>key</code>. After this, the newly updated entry will be returned.</li>
</ul>
</li>
<li>If not found, the <code>insert</code> function from the <code>handler</code> is used to generate a new <code>value</code>. This will be assigned to the given <code>key</code> before returning it.</li>
<li>In either case, the <code>value</code> will be the <code>return value</code> of the <code>upsert</code> method.</li>
</ol>
<p> <strong>What is the motivation?</strong> Adding and updating values of a <code>Map</code> are tasks that developers often perform in conjunction. There are currently no <code>Map</code> methods for either of those two things, let alone a method that does both. The workarounds involve multiple lookups and can be inconvenient for developers. It is also aiming to prevent code that might be confusing or lead to errors.</p>
<details>
<summary>
Either update or insert for a specific key
</summary>
<p> Before:</p>
<pre><code class="language-js">// two lookups
old = map.get(key);
if (!old) {
map.set(key, value);
} else {
map.set(key, updated);
}
</code></pre>
<p> Using upsert:</p>
<pre><code class="language-js">map.upsert(key, {
update: () => updated,
insert: () => value
});
</code></pre>
</details>
<details>
<summary>
Just insert if missing:
</summary>
<p> Before:</p>
<pre><code class="language-js">// two lookups
if (!map1.has(key)) {
map1.set(key, value);
}
</code></pre>
<p> Using upsert:</p>
<pre><code class="language-js">map.upsert(key, {
insert: () => value
});
</code></pre>
</details>
<details>
<summary>
Just update if present:
</summary>
<p> Before:</p>
<pre><code class="language-js">// three lookups
if (map.has(key)) {
old = map.get(key);
updated = old.doThing();
map.set(key, updated);
}
</code></pre>
<p> Using upsert:</p>
<pre><code class="language-js"> map.upsert(key, {
update: (old) => old.doThing()
});
</code></pre>
</details></li>
</ul>
</div>
</section>
<section class="collapsible" id="installation">
<h2>Installing Mozilla Unified</h2>
<div class="content-body">
<p> In this section you will learn how to download the Mozilla environment based on your operating system. It will also feature setting up SpiderMonkey for development and introduce main tools which are used during development.</p>
<h3 id="1-installation-of-spidermonkey-and-required-tools">1. Installation of SpiderMonkey and required tools</h3>
<p> We will start by installing SpiderMonkey and all required tools.</p>
<p> Before you start installing, open a terminal and navigate to the desired location of the <code>mozilla_unified</code> folder.</p>
<p> The installation process depends on your operating system, therefore you can click on the link under that matches yours.</p>
<ul>
<li><a href="https://firefox-source-docs.mozilla.org/setup/linux_build.html" target="_blank">Build Mozilla Firefox on Linux</a></li>
<li><a href="https://firefox-source-docs.mozilla.org/setup/macos_build.html" target="_blank">Build Mozilla Firefox on Mac</a></li>
<li><a href="https://firefox-source-docs.mozilla.org/setup/windows_build.html" target="_blank">Build Mozilla Firefox on Windows</a></li>
</ul>
<p>During the installation, you will be asked which version of Firefox you want to build as a standard. In this tutorial we will choose <code>5: SpiderMonkey JavaScript™ engine</code>, which will allow for faster builds during development.</p>
<p>It doesn't matter if you choose to use <code>hg</code> or <code>git</code> to grab the source code.</p>
<p><strong>Having trouble?</strong></p>
<p><a href="https://firefox-source-docs.mozilla.org/setup/common_build_errors.html" target="_blank">Here</a> are some of the most common build errors. <strong>Note!</strong> Many errors can be related to your Python build. Ensure you are using the correct python path and/or configuration.</p>
<h3 id="2-running-spidermonkey">2. Running SpiderMonkey</h3>
<p>After the installation is complete, a folder named <code>mozilla-unified</code> should appear in the folder where your terminal was located when you started the guide above.</p>
<p> Navigate into <code>mozilla-unified</code> folder using <code>cd mozilla_unified</code>.</p>
<p> In order to run the SpiderMonkey engine, we first have to build it:</p>
<pre><code class="language-sh">./mach build
</code></pre>
<p> After executing this command the output should look something like this:</p>
<pre><code class="language-sh">Your build was successful!
To take your build for a test drive, run: |mach run|
</code></pre>
<p> In order to run the finished build, execute this command:</p>
<pre><code class="language-sh">./mach run
</code></pre>
<p> Your terminal should now enter the JavaScript™ Read-Eval-Print-Loop mode.
The functionality is similar to a browsers console and arbitrary JavaScript™ code can be executed. </p>
<pre><code class="language-sh">js>
</code></pre>
<p> This will be used to test our implementation throughout the tutorial.</p>
<p> You can use it to write js-lines to evaluate. This will output <code>Hello World!</code> in the console:</p>
<pre><code class="language-sh">js> console.log("Hello World!");
</code></pre>
<p> You can also execute <code>.js</code> files, which is done by giving the filename as a parameter in the <code>./mach run</code> command: </p>
<p> If you create a file called <code>helloworld.js</code> with <code>console.log("Hello World!);</code> in it and save it. You can execute it like this (given it is in the same folder):</p>
<pre><code class="language-sh">./mach run helloworld.js
</code></pre>
<h3 id="3-applying-simple-changes">3. Applying simple changes</h3>
<p> Self-hosted code is located in <code>mozilla-unified/js/src/builtin</code>. Here we can edit, add or remove functions.</p>
<p> To see the effect of this, we can change the <code>return value</code> of a function.</p>
<p> Open file <code>Array.js</code> and change function <code>ArrayAt</code> to <code>return</code> 42.</p>
<p> Test your changes by rebuilding and running the SpiderMonkey and then call the function with valid parameters.</p>
<pre><code class="language-sh"> js> var l = [1,2,3];
js> l.at(1);
42
</code></pre>
<p> Self-hosted code is a bit different to normal JavaScript™, given that you can effectively and easily edit/create functions you want.
This can cause problems, more on this later.</p>
</div>
</section>
<section class="collapsible" id="specification">
<h2>How to Read the ECMA-262 Language Specification</h2>
<div class="content-body">
<h3 id="1-what-is-the-ecma-262-specification">1. What is the ECMA-262 Specification?</h3>
<ul>
<li><a href="https://262.ecma-international.org/15.0/index.html?_gl=1*chzpt6*_ga*Mzc5OTUzMzY4LjE3MjQzMjMwMjA.*_ga_TDCK4DWEPP*MTczMDcyMzg1Ni41LjEuMTczMDcyNDYxMy4wLjAuMA" target="_blank">ECMA-262</a> is the official document that defines how JavaScript™ works. It provides detailed guidelines for developers and browser vendors on how JavaScript™ should behave in every situation, ensuring consistency and compatibility across different platforms and implementations.</li>
</ul>
<h3 id="2-how-to-navigate-the-document">2. How to Navigate the Document</h3>
<ul>
<li><strong>Start with the Table of Contents</strong>: This is where you’ll find major sections like grammar, types, and functions. It helps you jump to the part you’re interested in.</li>
<li><strong>Use Search</strong>: The specification is large. If you’re looking for a specific topic, like <code>Promise</code> or <code>Array</code>, use your browser’s search function (<code>Ctrl + F</code>/<code>cmd + F</code>) to find it quickly.</li>
<li><strong>Annexes (Extras)</strong>: At the end of the document, you’ll find extra sections that explain older features or give additional context.</li>
</ul>
<h3 id="3-how-to-read-the-algorithms">3. How to Read the Algorithms</h3>
<ul>
<li><strong>Algorithms are like instructions</strong>: The spec breaks down how JavaScript™ works using step-by-step instructions, almost like a recipe.</li>
<li><strong>Steps to follow</strong>: The specification breaks down methods like <a href="https://262.ecma-international.org/15.0/index.html?_gl=1*chzpt6*_ga*Mzc5OTUzMzY4LjE3MjQzMjMwMjA.*_ga_TDCK4DWEPP*MTczMDcyMzg1Ni41LjEuMTczMDcyNDYxMy4wLjAuMA#sec-array.prototype.push" target="_blank"><code>Array.prototype.push</code></a> into clear, numbered steps. For instance, it describes how the method first checks the current <code>length</code>, then adds the new element, and finally updates the array's <code>length</code>.</li>
<li><strong>Conditions</strong>: You’ll often see <code>if</code>-statements, that will tell you how to proceed if the statement evaluates to <code>true</code> or <code>false</code>.</li>
</ul>
<h3 id="4-some-key-symbols-and-what-they-mean">4. Some Key Symbols and What They Mean</h3>
<ul>
<li><strong><code>[[ ]]</code> (Double Square Brackets)</strong>: These represent internal properties of JavaScript™ objects. These are properties that JavaScript™ uses internally but developers can’t directly access.</li>
<li><strong><code>?</code> (Question Mark)</strong>: This shorthand means "if this operation results in an error (abrupt completion), <code>return</code> that error immediately." For example, <code>? Call(func, arg)</code> means that if calling <code>func</code> with <code>arg</code> throws an error, stop the current process and <code>return</code> the error right away.</li>
<li><strong><code>Return</code></strong>: This marks the end of an operation, specifying the result to be returned.</li>
<li><strong><code>{ }</code> (Curly braces)</strong>: These are used to define a <strong>Record</strong> structure. A <strong>Record</strong> is a data structure that groups together related fields as <code>key-value</code> pairs. Each field is identified by a name (<code>key</code>) and stores a specific <code>value</code>. </li>
<li><strong>Keywords</strong>: Keywords like <code>If</code>, <code>Else</code>, or <code>Else if</code> are represented as <strong>algorithmic steps</strong> in plain text, rather than in code syntax, to describe the behavior that an implementation should follow.</li>
</ul>
<h3 id="5-finding-information-on-other-symbols">5. Finding Information on Other Symbols</h3>
<p>The specification text uses a range of notations and symbols to describe its syntax and structure. To understand these symbols, you can look into this specific section in the specification:</p>
<ul>
<li><a href="https://262.ecma-international.org/15.0/index.html?_gl=1*chzpt6*_ga*Mzc5OTUzMzY4LjE3MjQzMjMwMjA.*_ga_TDCK4DWEPP*MTczMDcyMzg1Ni41LjEuMTczMDcyNDYxMy4wLjAuMA#sec-notational-conventions" target="_blank">Notational Conventions</a>: This section explains the different types of symbols, and how they are used to define the language. </li>
<li>For example, in the <a href="https://262.ecma-international.org/15.0/index.html?_gl=1*chzpt6*_ga*Mzc5OTUzMzY4LjE3MjQzMjMwMjA.*_ga_TDCK4DWEPP*MTczMDcyMzg1Ni41LjEuMTczMDcyNDYxMy4wLjAuMA#sec-nonterminal-symbols-and-productions" target="_blank">Nonterminal Symbols and Productions</a> section, you can read about nonterminal symbol, which are shown in <em>italic type</em>, and learn how to read the syntactic definition of a <strong>WhileStatement</strong>.</li>
</ul>
<h3 id="6-start-simple">6. Start Simple</h3>
<ul>
<li>Avoid diving into the complex parts right away. Start with sections like the <strong>Introduction</strong> or common JavaScript™ features, such as arrays and functions.</li>
<li><strong>External Help</strong>: Use resources like <a href="https://searchfox.org/" target="_blank">SearchFox.org</a> to browse and search for JavaScript™ engine implementations or additional explanations before checking the more technical spec.</li>
<li>You can also check out <a href="https://timothygu.me/es-howto/">https://timothygu.me/es-howto/</a> or <a href="https://v8.dev/blog/tags/understanding-ecmascript">https://v8.dev/blog/tags/understanding-ecmascript</a> for other helpful guides on how to read the ECMA-262 Language Specification.</li>
</ul>
<h3 id="7-example-understanding-arrayprototypepush">7. Example: Understanding <a href="https://262.ecma-international.org/15.0/index.html?_gl=1*chzpt6*_ga*Mzc5OTUzMzY4LjE3MjQzMjMwMjA.*_ga_TDCK4DWEPP*MTczMDcyMzg1Ni41LjEuMTczMDcyNDYxMy4wLjAuMA#sec-array.prototype.push" target="_blank"><code>Array.prototype.push</code></a></h3>
<ul>
<li>In the specification, you can search for <code>Array.prototype.push</code> to see how it works. The algorithm will explain:<ul>
<li>First, the <code>length</code> of the array is checked.</li>
<li>Then, the new element is added to the array.</li>
<li>Finally, the <code>length</code> property is updated to reflect the added element.</li>
</ul>
</li>
</ul>
<h3 id="the-mapprototypeupsert-specification">The <code>Map.prototype.upsert</code> Specification</h3>
<p>This is the specification of the <code>Map.prototype.upsert</code> which will be implemented in this tutorial. This specification will guide our implementation, detailing each operation the <code>upsert</code> method needs to perform to insert or update a <code>key-value</code> pair in a <code>Map</code> object.</p>
<pre><code class="language-lua">1. Let M be the this value.
2. Perform ? RequireInternalSlot(M, [[MapData]]).
3. Let entries be the List that is M.[[MapData]].
4. For each Record { [[Key]], [[Value]] } e that is an element of entries, do
4a. If e.[[Key]] is not empty and SameValueZero(e.[[Key]], key) is true, return e.[[Value]].
4ai. If HasProperty(handler, "update") is `true`, then
4ai1. Let updateFn be ? Get(handler, "update").
4ai2. Let updated be ? Call(updateFn, handler, « e.[[Value]], key, M »).
4ai3. Set e.[[Value]] to updated.
4aii. Return e.[[Value]].
5. Let insertFn be ? Get(handler, "insert").
6. Let inserted be ? Call(insertFn, handler, « e.[[Value]], key, M »).
7. Set e.[[Value]] to inserted.
8. Return e.[[Value]].
</code></pre>
<p>An html version of the specification can be found <a href="https://bldl.github.io/upsert-tutorial/initial-emplace-spec/Map.prototype.emplace.html" target="_blank">here.</a></p>
<p>The ECMAScript 262 specification text can look intimidating at first glance. Before starting the implementation, try to get a rough understanding of what each line in the spec means. Write pseudocode, sentences or a combination.
The aim is to develop a clear understanding of the functionality we want to achieve.</p>
<p><strong>Key Points to Focus On:</strong></p>
<ul>
<li><strong>Scope and Validation:</strong> The first few lines establish the <code>this value</code> (<code>M</code>) and ensure it’s a valid instance of <code>Map</code>.</li>
<li><strong>Iterating Over Entries:</strong> The method iterates through the <code>Map</code> entries to check if the specified <code>key</code> already exists.</li>
<li><strong>Conditional Update or Insert:</strong> If the key exists, it checks for an <code>update</code> function in the <code>handler</code> and applies it to update the <code>value</code>. If the key does not exist, it uses the <code>insert</code> function to create a new entry.</li>
<li><strong>Returning the Result:</strong> Finally, it returns the updated or inserted <code>value</code>.</li>
</ul>
<p>By breaking down the specification in this way, you'll have a roadmap for implementing each part of the <code>upsert</code> method. This approach will help make the implementation process smoother and ensure that you understand how each step contributes to the overall functionality.</p>
<p><strong>Rewrite the spec in your own words</strong></p>
<p>Example:
3. Let <strong>entries</strong> be the <code>List</code> that is <strong>M</strong>.[[MapData]].</p>
<p>Could be rewritten to:
3. make a <code>List</code> variable <strong>entries</strong>, which stores pairs <code>(key, value)</code></p>
<p>In the implementation part of this tutorial, each line of the specification will be explained.</p>
</div>
</section>
<section class="collapsible" id="searchfox">
<h2>Using Searchfox</h2>
<div class="content-body">
<p> <a href="https://searchfox.org/" target="_blank">Searchfox</a> is a helpful tool. Searchfox provides an indexed view of the source code, allowing developers to efficiently search for specific files, functions, or keywords. For instance, you can trace the implementation of existing JavaScript™ features, see how certain functions interact with SpiderMonkey’s internal data structures, or find how built-in JavaScript™ objects like <code>Map</code> are handled. SearchFox helps you navigate a seemingly endless and confusing codebase.</p>
<p> When Implementing the <code>upsert</code> proposal, you will find that looking at existing implementations of similar functionality is often a good starting point. Combine the ECMA-262 Specification with Searchfox and look at existing code.</p>
<p> Example workflow:</p>
<ol>
<li><em>Some line from the specification</em>.</li>
<li>Find some other function with the same or similar spec line in the ECMA-262 specification.</li>
<li>Look up the function in Searchfox.</li>
<li>Borrow from the other function.</li>
</ol>
</div>
</section>
<section class="collapsible" id="implementation">
<h2>Implementation</h2>
<div class="content-body">
<p>In this section, we’ll walk through the process of implementing the <code>Map.prototype.upsert</code> method step-by-step. We will examine each line of the specification in detail and you will gain a deep understanding of the implementation process. By the end, you’ll have a fully functional <code>upsert</code> method in JavaScript™, along with insight in where to find resources and information which gives you a strong foundation to implement additional functions on your own in the future.</p>
<h3 id="creating-a-function">Creating a function</h3>
<p> The first step to implementing a function in SpiderMonkey is to create a <em>hook</em> in C++. This hook serves as the connection between SpiderMonkey’s C++ core and our self-hosted JavaScript™ code.</p>
<p> The JavaScript™ type <code>Map</code> is defined in C++ as <code>MapObject</code>. All <code>Map</code> methods, like <code>Map::set</code> and <code>Map::get</code>, are defined
in the array <code>MapObject::methods[]</code>. To add <code>upsert</code> we need to define a hook in this array.</p>
<p> <strong>Create a hook in <code>MapObject.cpp</code>:</strong></p>
<pre><code class="language-cpp">
JS_SELF_HOSTED_FN("upsert", "MapUpsert", 2,0),
</code></pre>
<p> This line sets up the hook with the following details:</p>
<ul>
<li><strong>JS_SELF_HOSTED_FN:</strong> Indicates the function is implemented in self-hosted JavaScript™ (meaning the main logic is in JavaScript™ rather than C++).</li>
<li><strong>First argument:</strong> <code>upsert</code> — the function name as it will appear in JavaScript™.</li>
<li><strong>Second argument:</strong> <code>MapUpsert</code> — the name of the JavaScript™ implementation (which we’ll define shortly)</li>
<li><strong>Third argument:</strong> <code>2</code> Number of arguments.</li>
<li><strong>Fourth argument:</strong> <code>0</code> Number of flags.</li>
</ul>
<p> <strong>Copy the line above and paste it into <code>MapObject.cpp</code> under <code>MapObject::Methods</code></strong></p>
<p> With the C++ hook in place, we can define the actual function in JavaScript™. Open <code>Map.js</code>, and add the following code:</p>
<pre><code class="language-js">function MapUpsert(key, handler) {
return 42
}
</code></pre>
<p> This is a simple placeholder function. It doesn’t perform any <code>upsert</code> logic yet; it just returns the number 42. This step allows us to check that our function is correctly hooked up and accessible in the JavaScript™ runtime.</p>
<p> To confirm everything is connected, build the project and run the JavaScript™ shell:</p>
<pre><code class="language-sh">./mach build
...
./mach run
</code></pre>
<p> Once the shell opens, you can test your upsert function:</p>
<pre><code class="language-sh">js> const m = new Map()
js> m.upsert(0,0)
42
</code></pre>
<p> If you see 42 as the output, then you’ve successfully created a function hook and defined an initial JavaScript™ implementation. This means we’re ready to move forward with implementing the actual <code>upsert</code> functionality.</p>
<h3 id="step-1---implement-the-first-line">Step 1 - Implement The First Line</h3>
<p> Now that we have our <code>upsert</code> method hooked up and accessible in the JavaScript™ runtime, it’s time to start implementing the logic as specified in the ECMAScript® proposal.</p>
<p> <strong>Setting Up <code>this</code> in the Function:</strong>
Some lines in the specification are more intuitive than others. The first line of the specification instructs us to capture the current <code>this</code> context, which is the <code>MapObject</code> instance on which <code>upsert</code> was called. This is a foundational step in almost any method, as it ensures we’re working with the correct object.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">1. Let M be the this value.
</code></pre>
<p> In the code, we can implement this line simply by assigning this to a variable called <code>M</code>. This will allow us to refer to the <code>Map</code> instance consistently throughout the function: </p>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
}
</code></pre>
<p> We have now captured the <code>this</code> object, which should be an instance of a <code>MapObject</code> and we can now start to manipulate this object in the upcoming steps.</p>
<h3 id="step-2---verify-the-object-type">Step 2 - Verify The Object Type</h3>
<p> With the <code>this</code> context now captured in <code>M</code>, our next step is to validate that <code>M</code> is actually a <code>MapObject</code>. This is crucial because JavaScript™ objects can sometimes be altered or misused, and we need to ensure that <code>upsert</code> is being called on a valid instance of <code>Map</code>. This verification process will prevent errors and misuse, keeping the method consistent with the ECMAScript® specification.</p>
<p> <strong>Verifying the Map’s Internal Structure</strong><br> The ECMAScript® specification uses internal slots to define hidden properties within objects. In this step, we’re asked to confirm that the object <code>M</code> has the <code>[[MapData]]</code> internal slot, which holds the actual <code>key-value</code> data for the <code>Map</code>. By checking for this internal slot, we can be confident that <code>M</code> is indeed a <code>Map</code> and not some other type of object.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">2. Perform ? RequireInternalSlot(M, [[MapData]]).
</code></pre>
<p> This step is common for most self-hosted <code>MapObject</code> methods. The solution for this step already exists in the code. Look at <code>MapForEach</code> as an example.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
}
</code></pre>
</details>
<h3 id="step-3---self-hosted-javascript™-vs-javascript™">Step 3 - Self-Hosted JavaScript™ vs. JavaScript™</h3>
<p> Before proceeding further in the tutorial, it’s imperative to improve our understanding of self-hosted JavaScript™. </p>
<p> All self-hosted JavaScript™ operates in <strong>strict mode,</strong> preventing functions from running in the global scope if invoked with a <code>null</code> or <code>undefined</code> scope. To make self-hosted JavaScript™ safe,
we have to follow some rules. A potentially critical problem when writing self-hosted code is <strong>monkey patching.</strong> This phenomenon occurs when our implementation makes a function call to an external function
which has been overwritten by user scripts. This problem can be mitigated by using <strong>function invocation.</strong> Use <code>callFunction</code> and <code>callContentFunction</code> to call function within the specific object scope.
Furthermore, self-hosted code also has limited access to the C++ builtins. Only a select set, defined in <code>Selfhosting.cpp</code> is accessible. </p>
<p> <strong>What functions can be used in self-hosted JavaScript™?</strong></p>
<ul>
<li>Other self-hosted functions (remember 'almost' everything is an <code>Object</code>).</li>
<li>Functions made accessible in <code>SelfHosting.cpp</code>.</li>
<li>Some abstract operations and additional utility functions.</li>
</ul>
<p> Read more about self-hosted code <a href="https://udn.realityripple.com/docs/Mozilla/Projects/SpiderMonkey/Internals/self-hosting" target="_blank">here.</a></p>
<p> <strong>The snippet below is from <code>SelfHosting.cpp</code> and displays the available <code>MapObject</code> builtins:</strong></p>
<pre><code class="language-cpp">
// Standard builtins used by self-hosting.
// Code snippet from SelfHosting.cpp
...
JS_FN("std_Map_entries", MapObject::entries, 0, 0),
JS_FN("std_Map_get", MapObject::get, 1, 0),
JS_FN("std_Map_set", MapObject::set, 2, 0),
...
</code></pre>
<p> <strong>Moving on with the implementation:</strong>
We have stored the <code>this</code> object and verified that is in fact an instance of <code>MapObject</code>. In the coming steps, the contents of this object will be manipulated. The next step tells us to store the contents of the <code>Map</code> as a <code>List</code>.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">3. Let entries be the List that is M.[[MapData]].
</code></pre>
<p> Use <code>callFunction</code> and the standard built-in <code>std_Map_entries</code> to retrieve a list of all <code>key-value</code> entries in the <code>Map</code>. Store it as a variable named <code>entries</code>.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
}
</code></pre>
</details>
<h3 id="step-4---iterating-through-the-map-entries">Step 4 - Iterating through the map entries</h3>
<p> Now that we’ve set up our initial structure and verified our <code>MapObject</code>, the next step is to iterate through the entries within the <code>Map</code>. This allows us to examine each <code>key</code>-<code>value</code> pair to determine
if the specified <code>key</code> already exists, which will help us decide whether to update an existing <code>value</code> or <code>insert</code> a new one. To achieve this we first have to set up an iteration of the <code>entries</code> list.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code>4. For each Record { [[Key]], [[Value]] } e that is an element of entries, do
</code></pre>
<p> Different methods of iteration is used in the other self-hosted <code>Map</code> methods. The specification states that we should use a <code>for-of loop</code>. Look at existing methods and create the for-loop. </p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
//...
}
}
</code></pre>
</details>
<p> <strong>Check if <code>key</code> already exists</strong></p>
<p> As mentioned above, the purpose of the iteration is to check whether the key already exists. This can be done by comparing the <code>key</code> with <code>eKey</code>.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">4a. If e.[[Key]] is not empty and SameValueZero(e.[[Key]], key) is true, then
</code></pre>
<p> The <code>SameValueZero</code> function helps us check for equality between the <code>key</code> provided to <code>MapUpsert</code> and the <code>key</code> in the current entry. This comparison ensures that we handle only the correct <code>key</code>-<code>value</code> pair.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
//...
}
}
}
</code></pre>
</details>
<p> If the <code>SameValueZero</code> operation returns <code>true</code> on an entry, the <code>key</code> exists in the <code>Map</code>. By logic of the specification, we cannot insert on an existing <code>key</code>-<code>value</code> pair, but we can <code>update</code> it this function exists in the <code>handler</code>.</p>
<p> <strong>Check for the <code>update</code> handler</strong></p>
<p> With the <code>key</code> identified in the <code>Map</code>, the next step is to determine if the <code>handler</code> object includes an <code>update</code> function. This will allow us to <code>update</code> the <code>value</code> associated with the existing <code>key</code> in the <code>Map</code>.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">4ai. If HasProperty(handler, "update") is `true`, then
</code></pre>
<p> In self-hosted JavaScript™, most objects are treated similarly to regular JavaScript™ objects, so we can use standard object methods for these checks. For example, <code>hasOwnProperty</code> can verify if <code>update</code> is a property of <code>handler</code>. </p>
<p> Here’s a snippet from the <code>Object.cpp</code> file displaying some self-hosted functions:</p>
<pre><code class="language-cpp">// Code snippet from Object.cpp
static const JSFunctionSpec object_methods[] = {
//...
JS_SELF_HOSTED_FN("toLocaleString", "Object_toLocaleString", 0, 0),
JS_SELF_HOSTED_FN("valueOf", "Object_valueOf", 0, 0),
JS_SELF_HOSTED_FN("hasOwnProperty", "Object_hasOwnProperty", 1, 0),
//...
JS_FS_END,
};
</code></pre>
<p> Using <code>hasOwnProperty</code> on <code>handler</code>, we can now verify if the <code>update</code> property is defined. This step ensures that we only proceed if <code>handler</code> actually provides an <code>update</code> function.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
if (callFunction(Object_hasOwnProperty, handler, 'update')) {
//...
}
}
}
}
</code></pre>
</details>
<p> <strong>Get the <code>update</code> function from the <code>handler</code></strong></p>
<p> If the <code>key</code> exists and <code>update</code> is specified, the next step is to retrieve the <code>update</code> function.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">4ai1. Let updateFn be ? Get(handler, "update").
</code></pre>
<p> Store the <code>update</code> function as a variable <code>updateFn</code>.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
if (callFunction(Object_hasOwnProperty, handler, 'update')) {
var updateFN = handler['update'];
//...
}
}
}
}
</code></pre>
<p> <strong>Call the update function</strong></p>
<p> Now that we’ve verified the existence of an <code>update</code> function in the <code>handler</code> object, the next step is to invoke this function to get the <code>updated</code> <code>value</code>.</p>
<p> <strong>Specification Line:</strong></p>
</details>
<pre><code class="language-lua">4ai2. Let updated be ? Call(updateFn, handler, « e.[[Value]], key, M »).
</code></pre>
<p> In this context, we need to call the <code>update</code> function on the current value associated with the <code>Map</code> entry. This involves passing <code>e.[[Value]]</code> (the existing <code>value</code>), <code>key</code>, and <code>M</code> as arguments to the function.</p>
<p> To perform this function call in self-hosted JavaScript™, we’ll use <code>callContentFunction</code>, to call <code>updateFn</code> with <code>M</code> as the scope and <code>eValue</code> (the existing <code>value</code>) and <code>key</code> as the arguments. The result of this call should be stored as <code>var updated</code>, which we’ll then use to update the <code>Map</code> entry. Why use <code>callContentFunction</code> instead of <code>callFunction</code>? <code>callFunction</code> is faster than <code>callContentFunction</code>, however the latter is safer with respect to user content. Since the <code>handler</code> object is passed by the user, <code>callContentFunction</code> is reasonable. You can read a more detailed explanation <a href="https://udn.realityripple.com/docs/Mozilla/Projects/SpiderMonkey/Internals/self-hosting" target="_blank">here</a>.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
if (callFunction(Object_hasOwnProperty, handler, 'update')) {
var updateFN = handler['update'];
var updated = callContentFunction(updateFN, M, eValue, key);
//...
}
}
}
}
</code></pre>
</details>
<p> <strong>Update the <code>value</code> in the <code>Map</code></strong></p>
<p> Once we have the <code>updated</code> <code>value</code> from calling the <code>update</code> function, we can proceed to replace the current <code>value</code> in the map entry.</p>
<p> <strong>Specification Line:</strong></p>
<pre><code class="language-lua">4ai3. Set e.[[Value]] to updated.
</code></pre>
<p> This step involves using the <code>std_Map_set</code> function, a standard self-hosted operation that allows us to safely set a new <code>value</code> for a specified <code>key</code> in the <code>Map</code>. Since <code>std_Map_set</code> is a built-in function available to self-hosted code, we’ll call it to update the entry with our newly computed <code>updated</code> <code>value</code>.</p>
<p> <strong>Recall the standard built-in map operations specified in <code>SelfHosting.cpp</code>:</strong></p>
<pre><code class="language-cpp">// Standard builtins used by self-hosting.
// Code snippet from SelfHosting.cpp
JS_FN("std_Map_entries", MapObject::entries, 0, 0),
JS_FN("std_Map_get", MapObject::get, 1, 0),
JS_FN("std_Map_set", MapObject::set, 2, 0),
</code></pre>
<p> Use <code>callFunction</code> and <code>std_Map_entries</code> to set the new <code>value</code>.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
if (callFunction(Object_hasOwnProperty, handler, 'update')) {
var updateFN = handler['update'];
var updated = callContentFunction(updateFN, M, eValue, key);
callFunction(std_Map_set, M, key, updated);
}
}
}
}
</code></pre>
<p> <strong><code>Return</code> the <code>value</code></strong></p>
<p> We have now <code>updated</code> the <code>value</code>, and can <code>return</code> it.</p>
<p> <strong>Specification Line:</strong></p>
</details>
<pre><code class="language-lua">4aii. Return e.[[Value]].
</code></pre>
<p> <code>return</code> the <code>updated</code> <code>value</code>.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
if (callFunction(Object_hasOwnProperty, handler, 'update')) {
var updateFN = handler['update'];
var updated = callContentFunction(updateFN, M, eValue, key);
callFunction(std_Map_set, M, key, updated);
}
return callFunction(std_Map_get, M, key);
}
}
}
</code></pre>
</details>
<h3 id="step-5---implementing-the-insert-handler">Step 5 - Implementing The <code>Insert</code> Handler</h3>
<p> In this step, we’ll handle the scenario where the specified <code>key</code> doesn’t exist in the <code>Map</code>.</p>
<p> <strong>The Specification States</strong></p>
<pre><code class="language-lua">5. Let insertFn be ? Get(handler, "insert").
6. Let inserted be ? Call(insertFn, handler, « e.[[Value]], key, M »).
7. Set e.[[Value]] to inserted.
8. Return e.[[Value]].
</code></pre>
<p> This section is similar to our approach for updating an existing entry, except here we’re adding a new entry to the <code>Map</code>. If the <code>key</code> isn’t found, we retrieve the <code>insert</code> function from the <code>handler</code> and invoke it to generate the initial <code>value</code> for this new <code>key</code>-<code>value</code> pair.</p>
<p> The section uses similar techniques to the <code>update</code> scenario. Use the knowledge and experience you have gained so far to implement the <code>insert</code> handler.</p>
<details>
<summary>Solution</summary>
<pre><code class="language-js">function MapUpsert(key, handler) {
var M = this;
if (!IsObject(M) || (M = GuardToMapObject(M)) === null) {
return callFunction(
CallMapMethodIfWrapped,
this,
key,
handler,
"MapUpsert"
);
}
var entries = callFunction(std_Map_entries, M);
for (var e of allowContentIter(entries)) {
var eKey = e[0];
var eValue = e[1];
if (SameValueZero(key, eKey)) {
if (callFunction(Object_hasOwnProperty, handler, 'update')) {
var updateFN = handler['update'];
var updated = callContentFunction(updateFN, M, eValue, key);
callFunction(std_Map_set, M, key, updated);
}
return callFunction(std_Map_get, M, key);
}
}
var insertFN = handler['insert'];
var inserted = callFunction(insertFN, key, M);
callFunction(std_Map_set, M, key, inserted);
return callFunction(std_Map_get, M, key);
}
</code></pre>
</details>
<h3 id="test-the-implementation">Test the implementation</h3>
<p> Now that we have implemented the function, it's essential that we test it to verify it behaves as intended.</p>
<p> Recall, you can create files and run them with the command:</p>
<pre><code class="language-sh">./mach run MyFileName.js
</code></pre>
<p> Create a script to test your implementation or use the sample script below:</p>
<details>
<summary>Script</summary>
<pre><code class="language-js"> console.log("Running tests for Map.prototype.upsert proposal...");
// Utility function for logging test results
function logResult(testName, actual, expected) {
console.log(`Test: ${testName}`);
console.log(`Expected: ${expected}`);
console.log(`Actual: ${actual}`);
console.log(actual === expected ? "Passed" : "Failed");
console.log('------------------------------');
}
// Test 1: Update on existing key
(function testUpdateExistingKey() {
const m = new Map();
m.set("key", "val");
m.upsert("key", {
update: () => "updated"
});
logResult("Update on existing key", m.get("key"), "updated");
})();
// Test 2: Insert on existing key (should not change existing value)
(function testInsertExistingKey() {
const m = new Map();
m.set("key", "val");
m.upsert("key", {
insert: () => "inserted"
});
logResult("Insert on existing key (no change)", m.get("key"), "val");
})();
// Test 3: Insert and update on existing key
(function testInsertAndUpdateExistingKey() {
const m = new Map();
m.set("key", "val");
m.upsert("key", {
update: () => "updated",
insert: () => "inserted"
});
logResult("Insert and update on existing key", m.get("key"), "updated");
})();
// Test 4: Update nonexistent key (should not update, no effect)
(function testUpdateNonexistentKey() {
const m = new Map();
try {
m.upsert("nonexistent", {
update: () => "updated"
});
} catch (e) {
console.log("Test: Update nonexistent key");
console.log("Expected Error: " + e.message);
console.log('------------------------------');
}
})();
// Test 5: Insert nonexistent key
(function testInsertNonexistentKey() {
const m = new Map();
m.upsert("nonexistent", {
insert: () => "inserted"
});
logResult("Insert nonexistent key", m.get("nonexistent"), "inserted");
})();
// Test 6: Insert and update nonexistent key (insert should happen)
(function testInsertAndUpdateNonexistentKey() {
const m = new Map();
m.upsert("nonexistent", {
update: () => "updated",
insert: () => "inserted"
});
logResult("Insert and update nonexistent key", m.get("nonexistent"), "inserted");
})();
// Test 7: Increment counter twice
(function testIncrementCounter() {
const counter = new Map();
counter.upsert("a", {
update: (v) => v + 1,
insert: () => 1
});
logResult("Increment counter first time", counter.get("a"), 1);
counter.upsert("a", {
update: (v) => v + 1,
insert: () => 1
});
logResult("Increment counter second time", counter.get("a"), 2);
})();
</code></pre>
</details>
</div>
</section>
<section class="collapsible" id="issues">
<h2>Issues with the Original Proposal</h2>
<div class="content-body">
<p><p>The proposal we have dealt with so far introduced a flexible solution by allowing both an <code>update</code> and an <code>insert</code> function, which added unnecessary complexity to the usage of <code>upsert</code>. Flexibility is generally a good thing. However in the case of <code>Map.prototype.upsert</code>, the flexibility comes at the expense of simplicity and ease of use, which is very important for widespread adoption in programming languages.</p>
<p>The process of checking if a <code>key</code> exists and then inserting it if not, is likely the primary, in-demand use case for this method. By following the steps of this proposal, the process became unnecessarily complicated. Most developers typically just need to insert a <code>value</code> if the given <code>key</code> is missing, rather than having to provide separate logic for both <code>insert</code> and <code>update</code>. </p>
<p>In addition, the approach of the original proposal doesn't align well with common practices in other known programming languages. An example which offers a similar and simpler functionality is seen in Python and is called <a href="https://docs.python.org/2/library/stdtypes.html#dict.setdefault" target="_blank"><code>setdefault</code></a>. You can read more about this method in the next section of the tutorial.</p>
<p>By making it overcomplicated and a feature that is not commonly found in other languages, the method is at risk at being underutilized. Reducing the scope to a more straightforward function makes it more intuitive and more likely to be used effectively. </p>
</p>
</div>
</section>
<section class="collapsible" id="new-proposal">
<h2>Explaining the New Proposal</h2>
<div class="content-body">
<p>The new <a href="https://github.com/tc39/proposal-upsert" target="_blank">proposal</a> presents the idea of implementing two different versions. </p>
<p> (1) Takes the arguments <code>key</code> and <code>value</code>. </p>
<p> (2) Takes the arguments <code>key</code> and <code>callbackfn</code></p>
<p> Both the respective versions with <code>value</code> and <code>callbackfn</code> serves the same principle as a <code>get</code> or <code>insert</code> if missing method on the <code>MapObject</code>. For the remainder of this tutorial we will focus on the <code>upsert(key, value)</code> version.</p>
<p> <strong>What is the motivation for a new proposal?</strong>
A common problem when using a <code>Map</code> is how to handle doing a <code>Map</code> entry when you're not sure if the <code>key</code> already exists in the <code>Map</code>. This can be handled by first checking if the <code>key</code> is present, and then inserting or updating depending upon the result, but this is both inconvenient for the developer, and less than optimal, because it requires multiple lookups in the <code>Map</code> that could otherwise be handled in a single call.</p>
<p> <strong>What is the solution?</strong>
A method that will check whether the given <code>key</code> already exists in the <code>Map</code>. If the <code>key</code> already exists, the <code>value</code> associated with the <code>key</code> is returned. Otherwise the new <code>key-value</code> pair is inserted in to the <code>Map</code>, before returning the newly input <code>value</code>.</p>
<p> <strong>Simple use of "new" upsert:</strong></p>
<pre><code class="language-js"> // Currently
let prefs = new getUserPrefs();
if (prefs.has("useDarkmode")) {
let darkMode = prefs.get("useDarkmode");
}
else {
prefs.set("useDarkmode", true);
darkMode = true; //Default value
}
// Using upsert
let prefs = new getUserPrefs();
prefs.upsert("useDarkmode", true); // Default to true
</code></pre>
<p>By using <code>upsert</code>, default values can be applied at different times, with the assurance that later defaults will not overwrite an existing <code>value</code>. This is obviously because the <code>key</code> already exists and will <code>return</code> the existing <code>key</code> instead of inserting or overwriting.</p>
<details>
<summary>
Similar functionality in Python
</summary>
As mentioned earlier in this tutorial, there are similar functionalities in other languages such as Python and its setdefault method. In our case we use upsert on Map's. The setdefault method is used on dictionaries, lets use a similar code example:
<pre><code class="language-python"># Without setdefault
prefs = {}
if "useDarkmode" not in prefs :
prefs["useDarkmode"] = True # Default value
dark_mode = prefs["useDarkmode"]
</code></pre>
<pre><code class="language-python"># Using setdefault
prefs = {}
prefs.setdefault("useDarkmode", True)
</code></pre>
</details>
<p>To implement the updated proposal, we first need to adapt the specification. </p>
<h3 id="a-draft-of-the-new-specification-for-the-proposal">A Draft of The New Specification For The Proposal.</h3>
<p>The new specification now looks like this. It draws similarities from the old specification and adapts to the newly specified behaviour we seek in the <code>Map.prototype.upsert</code> method.</p>
<pre><code class="language-lua">1. Let M be the this value.
2. Perform ? RequireInternalSlot(M, [[MapData]]).
3. Let entries be the List that is M.[[MapData]].
4. For each Record { [[Key]], [[Value]] } e that is an element of entries, do
4a. If e.[[Key]] is not empty and SameValueZero(e.[[Key]], key) is true, return e.[[Value]].
5. Set e.[[Value]] to value.
6. Return e.[[Value]].
</code></pre>
<p> The next section will be based on this draft of the new specification. Later in the tutorial we will look into how we can write the specification in ecmarkup.</p>
<p> An html version of the specification can be found <a href="https://bldl.github.io/upsert-tutorial/key-value-callback-spec/Map.prototype.getOrInsert.html" target="_blank">here.</a></p>
</div>
</section>
<section class="collapsible" id="implementing-new-proposal">
<h2>Implementing the New Proposal</h2>
<div class="content-body">
<p>In this section, we will adapt our implementation to match the updated proposal specification. Fortunately, some of the logic from the previous implementation can be reused. Our goal here is to keep the code clean and efficient by making only the necessary adjustments.</p>