-
Notifications
You must be signed in to change notification settings - Fork 5
/
maps.html
458 lines (357 loc) · 30.6 KB
/
maps.html
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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="keywords" content="maps, records, dict, proplists, EEP, key value, semantics, data structure" />
<meta name="description" content="A guide to understanding Erlang Maps" />
<meta name="google-site-verification" content="mi1UCmFD_2pMLt2jsYHzi_0b6Go9xja8TGllOSoQPVU" />
<link rel="stylesheet" type="text/css" href="static/css/screen.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shCore.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shThemeLYSE2.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/print.css" media="print" />
<link href="rss" type="application/rss+xml" rel="alternate" title="LYSE news" />
<link rel="icon" type="image/png" href="favicon.ico" />
<link rel="apple-touch-icon" href="static/img/touch-icon-iphone.png" />
<link rel="apple-touch-icon" sizes="72x72" href="static/img/touch-icon-ipad.png" />
<link rel="apple-touch-icon" sizes="114x114" href="static/img/touch-icon-iphone4.png" />
<title>Postscript: Maps | Learn You Some Erlang for Great Good!</title>
</head>
<body>
<div id="wrapper">
<div id="header">
<h1>Learn you some Erlang</h1>
<span>for great good!</span>
</div> <!-- header -->
<div id="menu">
<ul>
<li><a href="content.html" title="Home">Home</a></li>
<li><a href="faq.html" title="Frequently Asked Questions">FAQ</a></li>
<li><a href="rss" title="Latest News">RSS</a></li>
<li><a href="static/erlang/learn-you-some-erlang.zip" title="Source Code">Code</a></li>
</ul>
</div><!-- menu -->
<div id="content">
<div class="noscript"><noscript>Hey there, it appears your Javascript is disabled. That's fine, the site works without it. However, you might prefer reading it with syntax highlighting, which requires Javascript!</noscript></div>
<h2>Postscript: Maps</h2>
<h3><a class="section" name="about-this-chapter">About This Chapter</a></h3>
<p>Oh hi. It's been a while. This is an add-on chapter, one that isn't yet part of the printed version of Learn You Some Erlang for great good! Why does this exist? Was the printed book a huge steaming pile of bad information? I hope not (though you can consult <a class="external" href="http://www.nostarch.com/erlang#updates">the errata</a> to see how wrong it is).</p>
<p>The reason for this chapter is that the Erlang zoo of data types has grown just a bit larger with the R17 release, and reworking the entire thing to incorporate the new type would be a lot of trouble. The R17 release had a lot of changes baked in, and is one of the biggest one in a few years. In fact, the R17 release should be called the 17.0 release, as Erlang/OTP then started changing their <code>R<Major>B<Minor></code> versioning scheme to a <a class="external" href="http://www.erlang.org/doc/system_principles/versions.html">different versioning scheme</a>.</p>
<p>In any case, as with most updates to the language that happened before, I've added notes and a few bits of content here and there through the website so that everything stays up to date. The new data type added, however, deserves an entire chapter on its own.</p>
<img class="right" src="static/img/eep-eep.png" width="238" height="299" alt="A weird road-runner representing EEP-43" />
<h3><a class="section" name="eep">EEP, EEP!</a></h3>
<p>The road that led to maps being added to the language has been long and tortuous. For years, people have been complaining about records and key/value data structures on multiple fronts in the Erlang community:</p>
<ul>
<li>People don't want to put the name of the record every time when accessing it and want dynamic access</li>
<li>There is no good canonical arbitrary key/value store to target and use in pattern matches</li>
<li>Something faster than <code>dict</code>s and <code>gb_trees</code> would be nice</li>
<li>Converting to and from key/value stores is difficult to do well, and native data types would help with this</li>
<li>Pattern matching is a big one, again!</li>
<li>Records are a preprocessor hack and problematic at run time (they are tuples)</li>
<li>Upgrades with records are painful</li>
<li>Records are bound to a single module</li>
<li>And so much more!</li>
</ul>
<p>There ended up being two competing proposals. The first is <a class="external" href="http://www.cs.otago.ac.nz/staffpriv/ok/frames.pdf">Richard O'Keefe's frames</a>, a really deeply thought out proposal on how to replace records, and Joe Armstrong's Structs (O'Keefe's proposal describes them briefly and compares them to frames).</p>
<p>Later on, the OTP team came up with <a class="external" href="http://www.erlang.org/eeps/eep-0043.html">Erlang Enhancement Proposal 43 (EEP-43)</a>, their own proposal for a "similar" data type. However, much like with community complaints, the two proposals were working on entirely different issues: maps are intended to be very flexible hash maps to replace <code>dict</code>s and <code>gb_trees</code>, and frames are intended to replace records. Maps would also ideally be able to replace records in some use cases, but not all of them while preserving the positive characteristics of records (as we'll see in <a class="section" href="maps.html#mexican-standoff">Mexican Standoff</a>).</p>
<p>Maps were the one picked to be implemented by the OTP team, and frames are still hanging with their own proposal, somewhere.</p>
<p>The proposal for maps is comprehensive, covers many cases, and highlights many of the design issues in trying to have them fit the language, and for these reasons, their implementation will be gradual. The specification is described in <a class="section" href="maps.html#what-maps-shall-be">What Maps Shall Be</a>, while the temporary implementation is described in <a class="section" href="maps.html#stubby-legs">Stubby Legs for Early Releases</a>.</p>
<h3><a class="section" name="what-maps-shall-be">What Maps Shall Be</a></h3>
<h4>The Module</h4>
<p>Maps are a data type similar to the <code>dict</code> data structure in intent, and has been given a module with a similar interface and semantics. The following operations are supported:</p>
<ul>
<li><code>maps:new()</code>: returns a new, empty map.</li>
<li><code>maps:put(Key, Val, Map)</code>: adds an entry to a map.</li>
<li><code>maps:update(Key, Val, Map)</code>: returns a map with <code>Key</code>'s entry modified or raises the error <code>badarg</code> if it's not there.</li>
<li><code>maps:get(Key, Map)</code> and <code>maps:find(Key, Map)</code>: find items in a map. The former returns the value if present or raises the <code>bad_key</code> error otherwise, whereas the latter returns <code>{ok, Val}</code> or <code>error</code>.</li>
<li><code>maps:remove(Key, Map)</code>: returns a map with the given element deleted. Similarly, <code>maps:without([Keys], Map)</code> will delete multiple elements from the returned map. If a key to be deleted isn't present, the functions behave as if they had deleted it — they return a map without it. The opposite of <code>maps:without/2</code> is <code>maps:with/2</code>.</li>
</ul>
<p>It also contains the good old functional standards such as <code>fold/3</code> and <code>map/2</code>, which work similarly to the functions in the <code>lists</code> module. Then there is the set of utility functions such as <code>size/1</code>, <code>is_map/1</code> (also a guard!), <code>from_list/1</code> and <code>to_list/1</code>, <code>keys/1</code> and <code>values/1</code>, and set functions like <code>is_key/2</code> and <code>merge/2</code> to test for membership and fuse maps together.</p>
<p>That's not all that exciting, and users want more!</p>
<h4>The Syntax</h4>
<p>Despite the promised gains in speed, the most anticipated aspect of maps is the native syntax they have. Here are the different operations compared to their equivalent module call:</p>
<table>
<thead>
<tr>
<td>Maps Module</td>
<td>Maps Syntax</td>
</tr>
</thead>
<tbody>
<tr>
<td><code>maps:new/1</code></td>
<td><code>#{}</code></td>
</tr>
<tr>
<td><code>maps:put/3</code></td>
<td><code>Map#{Key => Val}</code></td>
</tr>
<tr>
<td><code>maps:update/3</code></td>
<td><code>Map#{Key := Val}</code></td>
</tr>
<tr>
<td><code>maps:get/2</code></td>
<td><code>Map#{Key}</code></td>
</tr>
<tr>
<td><code>maps:find/2</code></td>
<td><code>#{Key := Val} = Map</code></td>
</tr>
</tbody>
</table>
<p>Bear in mind that <a class="section" href="maps.html#stubby-legs">not all of this syntax has been implemented yet</a>. Fortunately for map users, the pattern matching options go wider than this. It is possible to match more than one item out of a map at once:</p>
<pre class="brush:eshell">
1> Pets = #{"dog" => "winston", "fish" => "mrs.blub"}.
#{"dog" => "winston","fish" => "mrs.blub"}
2> #{"fish" := CatName, "dog" := DogName} = Pets.
#{"dog" => "winston","fish" => "mrs.blub"}
</pre>
<p>Here it's possible to grab the contents of any number of items at a time, regardless of order of keys. You'll note that elements are set with <code>=></code> and matched with <code>:=</code>. The <code>:=</code> operator can also be used to update an <em>existing</em> key in a map:</p>
<pre class="brush:eshell">
3> Pets#{"dog" := "chester"}.
#{"dog" => "chester","fish" => "mrs.blub"}
4> Pets#{dog := "chester"}.
** exception error: bad argument
in function maps:update/3
called as maps:update(dog,"chester",#{"dog" => "winston","fish" => "mrs.blub"})
in call from erl_eval:'-expr/5-fun-0-'/2 (erl_eval.erl, line 257)
in call from lists:foldl/3 (lists.erl, line 1248)
</pre>
<p>There's more matching in the specification, although it's not available in 17.0 yet:</p>
<pre class="brush:eshell">
5> #{"favorite" := Animal, Animal := Name} = Pets#{"favorite" := "dog"}.
#{"dog" => "winston","favorite" => "dog","fish" => "mrs.blub"}
6> Name.
"winston"
</pre>
<p>Within the same pattern, a known key's value can be used to define a variable (<var>Animal</var>) usable as another key, and then use that other key to match the desired value (<var>Name</var>). The limitation with this kind of pattern is that there cannot be cycles. For example, matching <code>#{X := Y, Y := X} = Map</code> cannot be done due to needing to know <var>Y</var> to match <var>X</var>, and needing to know <var>X</var> to bind <var>Y</var>. You also cannot do matching of a key by value (<code>#{X := val} = Map</code>) because there could be multiple keys with the same value.</p>
<div class="note">
<p><strong>Note:</strong> The syntax for accessing a single value (<code>Map#{Key}</code>) is documented as such in the EEP, but is subject to change in the future once implemented, and might be dropped entirely in favor of different solutions.</p>
</div>
<p>There's something interesting but unrelated that was added with maps. If you remember in <a class="chapter" href="starting-out-for-real.html#list-comprehensions">Starting Out For Real</a>, we introduced list comprehensions:</p>
<pre class="brush:eshell">
7> Weather = [{toronto, rain}, {montreal, storms}, {london, fog},
7> {paris, sun}, {boston, fog}, {vancouver, snow}].
[{toronto,rain},
{montreal,storms},
{london,fog},
{paris,sun},
{boston,fog},
{vancouver,snow}]
8> FoggyPlaces = [X || {X, fog} <- Weather].
[london,boston]
</pre>
<p>The same kind of stuff can be done with map comprehensions, once they're made available:</p>
<pre class="brush:eshell">
9> Weather = #{toronto => rain, montreal => storms, london => fog,
9> paris => sun, boston => fog, vancouver => snow}.
#{boston => fog,
london => fog,
montreal => storms,
paris => sun,
toronto => rain,
vancouver => snow}
10> FoggyPlaces = [X || X := fog <- Weather].
[london,boston]
</pre>
<p>Here, <code>X := fog <- Weather</code> represents a map generator, of the form <code>Key := Val <- Map</code>. This can be composed and substituted the same way list generators and binary generators can. Map comprehensions can also generate new maps:</p>
<pre class="brush:eshell">
11> #{X => foggy || X <- [london,boston]}.
#{boston => foggy, london => foggy}
</pre>
<p>Or to implement the map operation from the <code>maps</code> module itself:</p>
<pre class="brush:erl">
map(F, Map) ->
#{K => F(V) || K := V <- Map}.
</pre>
<p>And that's most of it! It's extremely refreshing to see this new data type joining the Erlang zoo, and hopefully users will appreciate it.</p>
<img class="right" src="static/img/bee.png" width="203" height="242" alt="A bee looking over a map" title="I'm grunting! what a pun, what a pun!" />
<h4>The Gritty Details</h4>
<p>The details aren't <em>that</em> gritty, just a little bit. The inclusion of maps in the language will impact a few things. EEP-43 goes into detail to define potential changes, many somewhat still in the air, for parts such as the distributed Erlang protocol, operator precedence, backwards compatibility, and suggestions to Dialyzer extensions (it is yet to see if the support will go as far as the EEP recommends).</p>
<p>Many of the changes have been proposed such that the user doesn't need to think very hard about them. One that is unavoidable, however, is sorting. Previously in the book, the sorting order was defined as:</p>
<pre class="expand">
number < atom < reference < fun < port < pid < tuple < list < bit string
</pre>
<p>Maps now fit in here:</p>
<pre class="expand">
number < atom < reference < fun < port < pid < tuple < map < list < bit string
</pre>
<p>Bigger than tuples and smaller than lists. Interestingly enough, maps can be compared to each other based on their keys and values:</p>
<pre class="brush:eshell">
2> lists:sort([#{ 1 => 2, 3 => 4}, #{2 => 1}, #{2 => 0, 1 => 4}]).
[#{2 => 1},#{1 => 4,2 => 0},#{1 => 2,3 => 4}]
</pre>
<p>The sorting is done similarly to lists and tuples: first by size, then by the elements contained. In the case of maps, these elements are in the sorted order of keys, and breaking ties with the values themselves.</p>
<div class="note koolaid">
<p><strong>Don't Drink Too Much Kool-Aid:</strong><br />
You may notice that while we can't update the key <code>1.0</code> of a map with the key <code>1</code>, it is possible to have them both compare as equal that way! This is one of the long-standing warts of Erlang. While it's very convenient to have all numbers compare equal from time to time, for example, when sorting lists so that <code>1.0</code> isn't greater than <code>9121</code>, this creates confusing expectations when it comes to pattern matching.</p>
<p>For example, while we can expect <code>1</code> to be equal to <code>1.0</code> (although not <em>strictly equal</em>, as with <code>=:=</code>), we can't expect to pattern match by doing <code>1 = 1.0</code>.</p>
<p>In the case of maps, this means that <code>Map1 == Map2</code> isn't a synonym of <code>Map1 = Map2</code>. Because Erlang maps respect Erlang sorting order, a map such as <code>#{1.0 => true}</code> is going to compare equal to <code>#{1 => true}</code>, but you won't be able to match them one against the other.</p>
</div>
<p>Be careful, because although the content of this section is written as based on EEP-43, the actual implementation might be lagging behind!</p>
<h3><a class="section" name="stubby-legs">Stubby Legs for Early Releases</a></h3>
<img class="right" src="static/img/corgi.png" width="375" width="263" alt="A Corgi with a cape, flying" />
<p>The maps implementation that came with Erlang 17.0 is complete, but only within the confines of the maps module. The major differences come from the syntax. Only a minimal subset of it is available:</p>
<ul>
<li>literal map declarations (<code>#{}</code>, <code>#{key1 => val1, "key2" => val2}</code>)</li>
<li>matching on known keys (<code>#{a := X} = SomeMap</code>)</li>
<li>Updating and adding elements with a known key in an existing map (<code>Map#{a := update, b => new}</code>)</li>
</ul>
<p>The rest, including accessing single values (<code>Map#{key}</code>) and using variables as keys, whether in matches or declarations, is still not there. Same fate for map comprehensions and Dialyzer support. In fact, the syntax may change and differ from the EEP in the end.</p>
<p>Maps are also still slower than most Erlang developers and the OTP team want them to be. Still, progress is ongoing and it should not take long — especially compared to how long it's been before maps were added to the language in the first place — before they get much better.</p>
<p>This early release is still good enough to get familiar with maps, and more importantly, get to know when and where to use them.</p>
<h3><a class="section" name="mexican-standoff">Mexican Standoff</a></h3>
<p>Everyone had their opinion on wanting native dictionaries or better records replacement as a priority. When maps were announced, many Erlang developers kind of just assumed that maps, when they came to the language, would solve the problem they wanted solved.</p>
<p>As such, there is some confusion on how maps should be used in Erlang.</p>
<h4>Maps Vs. Records Vs. Dicts</h4>
<p>To get it out of the way directly, maps are a replacement for dicts, not records. This can be confusing. At the beginning of this chapter, I identified common complaints, and it turns out that many of the complaints about records would be fixed by maps:</p>
<table>
<thead>
<tr>
<td>Issue Solved By Maps</td>
<td>Issue in Records</td>
<td>Issue in Dicts</td>
</tr>
</thead>
<tbody>
<tr>
<td>Record Name is cumbersome</td>
<td>✓</td>
<td> </td>
</tr>
<tr>
<td>No good native k/v store</td>
<td> </td>
<td>✓</td>
</tr>
<tr>
<td>Faster k/v store</td>
<td> </td>
<td>future</td>
</tr>
<tr>
<td>Converting easier with native type</td>
<td>✓</td>
<td>✓</td>
</tr>
<tr>
<td>More powerful pattern matching</td>
<td> </td>
<td>✓</td>
</tr>
<tr>
<td>Upgrades with Records</td>
<td>maybe</td>
<td> </td>
</tr>
<tr>
<td>Usable across modules without includes</td>
<td>✓</td>
<td> </td>
</tr>
</tbody>
</table>
<img class="right" src="static/img/mexican-standoff.png" width="254" height="237" alt="squid aiming two guns mexican-standoff-like, while eating a taco" />
<p>The score is pretty close. The point of maps being faster isn't necessarily the case yet, but optimizations should bring them to a better level. The OTP team is respecting the old slogan: first make it work, then make it beautiful, and only if you need to, make it fast. They're getting semantics and correctness out of the way first.</p>
<p>For upgrades with records being marked as 'maybe', this has to do with the <code>code_change</code> functionality. A lot of users are annoyed by having to convert records openly when they change versions similar to what we did with <a class="source" href="static/erlang/processquest/apps/processquest-1.1.0/src/pq_player.erl">pq_player.erl</a> and its upgrade code. Maps would instead allow us to just add fields as required to the entry and keep going. The counter-argument to that is that a botched up upgrade would crash early with records (a good thing!) whereas a botched up upgrade with maps may just linger on with the equivalent of corrupted data, and not show up until it's too late.</p>
<p>So why is it we should use maps as dicts and not as records? For this, a second table is necessary. This one is about <em>semantics</em>, and which data structure or data type a feature applies to:</p>
<table>
<thead>
<tr>
<td>Operations</td>
<td>Records</td>
<td>Maps</td>
<td>Dict</td>
</tr>
</thead>
<tbody>
<tr>
<td>Immutable</td>
<td>✓</td>
<td>✓</td>
<td>✓</td>
</tr>
<tr>
<td>Keys of any type</td>
<td></td>
<td> ✓</td>
<td> ✓</td>
</tr>
<tr><td>Usable with maps/folds </td><td> </td><td>✓</td><td>✓</td></tr>
<tr><td>Content opaque to other modules</td><td>✓</td><td> </td><td> </td></tr>
<tr><td>Has a module to use it </td><td> </td><td>✓</td><td>✓</td></tr>
<tr><td>Supports pattern matching </td><td>✓</td><td>✓</td><td> </td></tr>
<tr><td>All keys known at compile-time </td><td>✓</td><td> </td><td> </td></tr>
<tr><td>Can merge with other instance </td><td> </td><td>✓</td><td>✓</td></tr>
<tr><td>Testing for presence of a key </td><td> </td><td>✓</td><td>✓</td></tr>
<tr><td>Extract value by key </td><td>✓</td><td>✓</td><td>✓</td></tr>
<tr><td>Per-key Dialyzer type-checking </td><td>✓</td><td>*</td><td> </td></tr>
<tr><td>Conversion from/to lists </td><td> </td><td>✓</td><td>✓</td></tr>
<tr><td>Per-element default values </td><td>✓</td><td> </td><td> </td></tr>
<tr><td>Standalone data type at runtime</td><td> </td><td>✓</td><td> </td></tr>
<tr><td>Fast direct-index access </td><td>✓</td><td> </td><td> </td></tr>
</tbody>
</table>
<p><em>* The EEP recommends making this possible for keys known at compile-time, but has no ETA on when or if this will happen.</em></p>
<p>This chart makes it rather obvious that despite the similarity in syntax between maps and records, dicts and maps are much closer together <em>semantically</em> than maps are to records. Therefore, using maps to replace records would be similar to trying to replace 'structs' in a language like C with a hash map. It's not impossible, but that doesn't mean it's a good idea given they often have different purposes.</p>
<p>Keys being known at compile time brings advantages with fast access to specific values (faster than what is possible dynamically), additional safety (crash early rather than corrupting state), and easier type checking. These make records absolutely appropriate for a process' internal state, despite the occasional burden of writing a more verbose <code>code_change</code> function.</p>
<p>On the other hand, where Erlang users would use records to represent complex nested key/value data structures (oddly similar to objects in object-oriented languages) that would frequently cross module boundaries, maps will help a lot. Records were the wrong tool for that job.</p>
<p>To put it briefly, places where records really felt out of place and cumbersome <em>could</em> be replaced by maps, but most record uses <em>should not</em> be replaced that way.</p>
<div class="note koolaid">
<p><strong>Don't Drink too Much Kool-Aid:</strong><br />
It is often tempting to bring complex nested data structures to the party and use them as one big transparent object to pass in and out of functions or processes, and to then just use pattern matching to extract what you need.</p>
<p>Remember, however, that there is a cost to doing these things in message passing: Erlang data is copied across processes and working that way might be expensive.</p>
<p>Similarly, passing big transparent objects in and out of functions should be done with care. Building a sensible interface (or API) is already difficult; if you marry yourself to your internal data representation, enormous amounts of flexibility for the implementation can be lost.</p>
<p>It is better to think about your interfaces and message-based protocols with great care, and to limit the amount of information and responsibility that gets shared around. Maps are a replacement for dicts, not for proper design.</p>
</div>
<p>There are also performance impacts to be expected. Richard O'Keefe mentions it in his proposal:</p>
<blockquote title="Richard O'Keefe, Frame Proposal, p.27">
<p>You can't make dict good for the record-like uses that frames are meant for without making it bad for its existing uses.</p>
</blockquote>
<p> And the EEP from the OTP team also mentions something similar:</p>
<blockquote title="EEP-43">
<p>When comparing maps with records the drawbacks are easily remedied by maps, however the positive effects <em>[sic]</em> is not as easy to replicate in a built-in data-type where values are determined at runtime instead of at compile time.</p>
<ul>
<li>Being faster than direct-indexing array, where indices and possibly the resulting value are determined at compile time, is hard. In fact it is impossible.</li>
<li>Memory model for maps where the efficiency is near that of records could be achieved by essentially using two tuples, one for keys and one for values as demonstrated in frames. This would impact performance of updates on maps with a large number of entries and thus constrain the capability of a dictionary approach.</li>
</ul>
</blockquote>
<p>For the core of your process loop, when you know all keys that should exist, a record would be a smart choice, performance-wise.</p>
<h4>Maps Vs. Proplists</h4>
<p>One thing that maps may beat are proplists. A proplist is a rather simple data structure that is extremely well-suited for options passed to a module.</p>
<p>The <code>inet:setopts/2</code> call can take a list of options of the form <code>[{active, true}, binary]</code>, and the file handling functions can take argument lists such as <code>[read, write, append, {encoding, utf8}]</code>, for example. Both of these option lists can be read using the <code>proplists</code> module, and terms such as <code>write</code> will be expanded as if they were written as <code>{write, true}</code>.</p>
<img class="left" src="static/img/scale.png" width="213" height="163" alt="a scale measuring proplists vs. maps" />
<p>Maps, with their single-key access (whenever implemented) will represent a similar way to define properties. For example, <code>[{active, true}]</code> can be expressed with maps as <code>#{active => true}</code>. This is equally cumbersome, but it will make reading the option much simpler, given you won't need to make a module call (thanks to <code>Opts#{active}</code>).</p>
<p>It's somewhat to be expected that option lists that are <em>mostly</em> pairs will be replaced by maps. On the other hand, literal options such as <code>read</code>, <code>write</code>, or <code>append</code> will remain much nicer with proplists, from the user's perspective.</p>
<p>Given that right now most functions that require options use proplists, keeping things going that way forward may be interesting for consistency's sake. On the other hand, when the options are mostly pairs, using the <code>proplists</code> module can get cumbersome fast. Ultimately, the author of a library will have to make a decision between what could be internal clarity of implementation or general ecosystem consistency. Alternatively, the author could decide to support both ways of doing things at once to provide a graceful path towards deprecation of proplists, if that's how they feel about it.</p>
<p>On the other hand, functions that used to pass proplists as a return value from functions should probably switch to maps. There is little legacy there, and usability should be much better for users that way.</p>
<div class="note">
<p><strong>Note:</strong> maps can use a clever trick to set many default values at once easily. Whereas proplists would require the use of <code>proplists:get_value(Key, List, Default)</code>, maps can use their <code>merge/2</code> function.</p>
<p>According to the specification, <code>merge/2</code> will fuse two maps together, and if two keys are the same, the second map's value will prevail. This allows to call <code>maps:merge(Default, YourMap)</code> and get the desired values. For example, <code>maps:merge(#{key => default, other => ok}, #{other => 42})</code> will result in the map <code>#{key => default, other => 42}</code>.</p>
<p>This makes for an extremely convenient way to attribute default values manually, and then you can just use the resulting map without worrying about missing keys.</p>
</div>
<h3><a class="section" name="revised-for-maps">How This Book Would Be Revised For Maps</a></h3>
<p>I wanted to add this section because I do not necessarily have the time to update the book in its entirety to retrofit maps back in at this point in time.</p>
<p>For most of the book, however, little would change. It would mostly be replacing calls to the <code>dict</code> module and to <code>gb_trees</code> (in cases where the smallest/largest values aren't frequently required) with inline maps syntax. Pretty much none of the records used within the book would be changed, for semantics' sake.</p>
<p>I may revisit a few modules once maps are stable and fully implemented, but in the mean time, many examples would be impractical to write that way given the partial maps implementation.</p>
<ul class="navigation">
<li><a href="conclusion.html" title="Previous chapter">< Previous</a></li>
<li><a href="contents.html" title="Index">Index</a></li>
<li style="visibility:hidden;"><a href="content.html" title="Next chapter">Next ></a></li>
</ul>
</div><!-- content -->
<div id="footer">
<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License Details"><img src="static/img/cc.png" width="88" height="31" alt="Creative Commons Attribution Non-Commercial No Derivative License" /></a>
<p>Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution Non-Commercial No Derivative License</p>
</div> <!-- footer -->
</div> <!-- wrapper -->
<div id="grass" />
<script type="text/javascript" src="static/js/shCore.js"></script>
<script type="text/javascript" src="static/js/shBrushErlang2.js%3F11"></script>
<script type="text/javascript">
SyntaxHighlighter.defaults.gutter = false;
SyntaxHighlighter.all();
</script>
</body>
</html>