Skip to content

Commit dab1cb0

Browse files
committed
Added new LFE Friday post.
1 parent ea8515f commit dab1cb0

6 files changed

Lines changed: 121 additions & 23 deletions

File tree

docs/lfe-friday/current-state.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ Against the full OTP core set of **190 publicly documented modules**, that's
108108
| lists:flatmap/2 | LFE Friday - lists:flatmap/2 |
109109
| lists:delete/2 | LFE Friday - lists:delete/2 |
110110

111+
### maps — 1 post
112+
113+
| Function | Post |
114+
|----------|------|
115+
| maps:get/3, maps:get/2 (find/2 demonstrated) | LFE Friday - maps:get/3 |
116+
111117
### ordsets — 5 posts
112118

113119
| Function | Post |

docs/lfe-friday/post-template.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ Today's LFE Friday «is/covers» [«module:function/arity»](https://www.erlang.
4040

4141
<!-- 3. HAPPY PATH — 3–7 calls, real unedited output, prompt is "> ". -->
4242
```lfe
43-
> («module»:«function» «args»)
43+
lfe> («module»:«function» «args»)
4444
«actual output»
45-
> («module»:«function» «args»)
45+
lfe> («module»:«function» «args»)
4646
«actual output»
4747
```
4848

@@ -55,7 +55,7 @@ Today's LFE Friday «is/covers» [«module:function/arity»](https://www.erlang.
5555
«Lead-in: what we're about to try and why it might misbehave.»
5656

5757
```lfe
58-
> («module»:«function» «bad args»)
58+
lfe> («module»:«function» «bad args»)
5959
exception error: «actual error»
6060
```
6161

@@ -74,16 +74,15 @@ exception error: «actual error»
7474
The `«module»` module also provides [«sibling/arity»](https://www.erlang.org/doc/apps/«app»/«module».html#«sibling»/«arity»), which «one-line contrast».
7575

7676
```lfe
77-
> («module»:«sibling» «args»)
77+
lfe> («module»:«sibling» «args»)
7878
«actual output»
7979
```
8080

8181
<!-- 7. CLOSE — arc posts: trail next week ("Next week, we will …").
8282
Standalone posts: a one-sentence practicality verdict ("While the
83-
odds are low you'll reach for this daily, …"). Then sign. -->
83+
odds are low you'll reach for this daily, …"). No signature line —
84+
the author byline is rendered at the top of the post. -->
8485
«Closing line.»
85-
86-
- «Surname»
8786
````
8887

8988
## Pre-publish checklist (from the style guide, §9)
@@ -96,7 +95,8 @@ The `«module»` module also provides [«sibling/arity»](https://www.erlang.org
9695
5. Exactly one deeper cut, ≤2 paragraphs.
9796
6. No headings/bullets in the body unless the post genuinely turns a
9897
corner; ~250–450 words incl. code (~650–1,100 for arc installments).
99-
7. Signature line; arc recap + trail lines if in an arc.
98+
7. No signature line (author byline is rendered at the top); arc recap +
99+
trail lines if in an arc.
100100
8. Frontmatter: permalink ↔ filename ↔ published_date agree;
101101
`written_for` filled; cover image from `/images/fridays/`; guidance
102102
comments deleted.

docs/lfe-friday/style-guide.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ Posts follow this sequence (the template encodes it):
8282
`queue:cons/2``queue:snoc/2`; the other arities. This is how the
8383
series teaches the *module*, not just the function.
8484

85-
7. **The sign-off.** Series-continuity line if in an arc ("Next week,
86-
we will…"), then the signature (§6).
85+
7. **The close.** Series-continuity line if in an arc ("Next week, we
86+
will…"). No signature line — the author is shown at the top of the post
87+
(§6).
8788

8889
Steps 3–6 can interleave — the corpus often probes, explains a little,
8990
probes again. The order above is the default, not a straitjacket.
@@ -161,8 +162,11 @@ exceptional posts.
161162

162163
- All shell blocks are fenced with ` ```lfe `; non-LFE shell commands
163164
(rarely needed) use ` ```shell `.
164-
- REPL lines start with the prompt `>` followed by one space. Show the
165-
output exactly as printed, unindented, on the lines following.
165+
- REPL lines start with the prompt `lfe>` followed by one space. Show the
166+
output exactly as printed, unindented, on the lines following. (The shell
167+
prompt changed from a bare `>` to `lfe>`; posts in the 2015–2016 archive
168+
still show the old bare prompt — leave those as-is, use `lfe>` going
169+
forward.)
166170
- Inline code uses single backticks: `` `lists:any/2` ``. (The 2015
167171
corpus used ``double backticks``; new posts use the modern
168172
convention.)
@@ -184,13 +188,17 @@ exceptional posts.
184188
Proctor, original author; Robert Virding, translator). **New original
185189
posts drop the per-post Proctor header** — the series' lineage is
186190
credited once, on the series/announcement page.
187-
- **Keep the signature ritual.** Close every post with a signature line:
188-
- Single author: `- McGreggor`
189-
- Adaptation or co-written post: original surname first, adapter
190-
second, honoring the old form: `- <Original>, <Adapter>`
191+
- **No closing signature.** New posts do *not* end with a `- Surname`
192+
sign-off. The author is rendered at the top of every post from the
193+
`data.author` frontmatter, so a trailing signature is redundant. (The
194+
2015–2016 archive keeps its `- Proctor, Robert` lines; the change applies
195+
from the 2026 revival forward.) End instead on the post's last line of
196+
prose — for arc installments, that's the "next week" trail.
191197
- The frontmatter `data.author` must name a key from
192198
`src/_data/authors.yml` (e.g. `duncan-mcgreggor`, `robert-virding`).
193-
Add new authors there before first use.
199+
Add new authors there before first use. For an adaptation or co-written
200+
post, credit the second contributor through the post's author/byline data,
201+
not a signature line.
194202

195203

196204
## 7. Frontmatter and file conventions
@@ -242,7 +250,8 @@ Required frontmatter (see the template for the full block):
242250
6. No headings/bullets unless the post genuinely turned a corner.
243251
7. Word count (incl. code): ~250–450 (standalone) / ~650–1,100 (arc
244252
installment).
245-
8. Signature line present; arc continuity lines present if in an arc.
253+
8. No closing signature line (the byline is rendered at the top of the
254+
post); arc continuity lines present if in an arc.
246255
9. Frontmatter complete; permalink ↔ filename ↔ published_date agree;
247256
`written_for` filled; cover image set.
248257

@@ -264,9 +273,9 @@ Required frontmatter (see the template for the full block):
264273
> short-circuit pay off by timing both halves of the same question:
265274
>
266275
> ```lfe
267-
> > (timer:tc 'lists 'any (list (lambda (x) (== (rem x 2) 1)) (lists:seq 2 200000 2)))
276+
> lfe> (timer:tc 'lists 'any (list (lambda (x) (== (rem x 2) 1)) (lists:seq 2 200000 2)))
268277
> #(171661 false)
269-
> > (timer:tc 'lists 'any (list (lambda (x) (== (rem x 2) 0)) (lists:seq 2 200000 2)))
278+
> lfe> (timer:tc 'lists 'any (list (lambda (x) (== (rem x 2) 0)) (lists:seq 2 200000 2)))
270279
> #(19 true)
271280
> ```
272281
>

docs/lfe-friday/writing-plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ original series started but never covered.
1919

2020
| Week | Module | Topic | Notes |
2121
|------|--------|-------|-------|
22-
| 1 | maps | maps:new/0, from_list/1, to_list/1, get/2, get/3, put/3 | Basics, comparison with dict |
22+
| 1 | maps | maps:new/0, from_list/1, to_list/1, put/3 | Basics, comparison with dict — get/2 & get/3 covered 2026-06-19 |
2323
| 2 | maps | maps:merge/2, merge_with/3, update/3, update_with/3 | Mutations, merge_with is OTP 24+ |
2424
| 3 | maps | maps:filter/2, filtermap/2, map/2, fold/3, foreach/2 | Functional operations |
2525
| 4 | maps | maps:keys/1, values/1, find/2, is_key/2, size/1, groups_from_list/2 | Querying and grouping |

src/css/site.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
layout: post.liquid
3+
title: "LFE Friday - maps:get/3"
4+
description: "Reopening LFE Friday with the safe map read: maps:get/3 hands back a default instead of crashing on a missing key — plus the one eager-evaluation gotcha to know."
5+
permalink: "/blog/tutorials/2026/06/19/0900-lfe-friday---mapsget3"
6+
categories: ["tutorials"]
7+
tags: [lfe friday, lfe, erlang, maps, defaults]
8+
published_date: 2026-06-19 09:00:00 +0000
9+
is_draft: false
10+
data:
11+
author: duncan-mcgreggor
12+
written_for:
13+
lfe: "2.2.1"
14+
erlang: "28"
15+
last_validated: 2026-06-18
16+
cover_image: "/images/fridays/LFE_Friday_00269_.png"
17+
cover_alt: "Vigdís — LFE Friday, LiffyBot reaching into a glowing key-value lattice in a Mead-style command room"
18+
math: false
19+
---
20+
21+
Today's LFE Friday digs into [`maps:get/3`](https://www.erlang.org/doc/apps/stdlib/maps.html#get/3) — and with it, quietly reopens the series after a rather long pause.
22+
23+
It has been a while. The last time LFE Friday reached for a key-value store, the answer was [`dict:merge/3`](/blog/tutorials/2015/03/01/1823-lfe-friday---dictmerge3), back in March 2015. `dict` still works. But it is no longer where modern LFE reaches first: the `maps` module landed in Erlang/OTP 17, a few months after this series first started, and somehow never got its turn here. So the revival starts where you actually start most days — pulling a value back out of a map without getting burned when the key isn't there. Over the next few Fridays we will walk the rest of the module; today, the safe read.
24+
25+
`maps:get/3` takes a key, a map, and a default value. It returns the value stored under that key — or, if the key is not in the map, the default you handed it. That third argument is the whole point: it is the difference between a lookup that answers and a lookup that crashes.
26+
27+
Let's build a little config map to read from.
28+
29+
```lfe
30+
lfe> (set config (map 'host "localhost" 'port 8080 'scheme 'http))
31+
#M(port 8080 scheme http host "localhost")
32+
```
33+
34+
Notice the shell hands the keys back in its own order, not the order we typed them in — `maps` are unordered, and the printed layout reflects the map's internal arrangement rather than our insertion order. Reading a key that exists, though, is unremarkable, which is exactly what you want from a read.
35+
36+
```lfe
37+
lfe> (maps:get 'host config)
38+
"localhost"
39+
lfe> (maps:get 'port config)
40+
8080
41+
```
42+
43+
Those are `maps:get/2`, the two-argument cousin. Now let's try to break it — what happens when we ask for a key that isn't there?
44+
45+
```lfe
46+
lfe> (maps:get 'timeout config)
47+
** exception error: #(badkey timeout)
48+
in (maps : get timeout #M(port 8080 scheme http host "localhost"))
49+
```
50+
51+
`maps:get/2` does not shrug and hand back `undefined` the way some languages' map lookups do. A missing key is an *error*`#(badkey timeout)` — and it will take down a process that wasn't expecting it. Often that strictness is precisely what you want. But just as often you have a sensible fallback in mind, and crashing is overkill. That is what the third argument buys you.
52+
53+
```lfe
54+
lfe> (maps:get 'timeout config 5000)
55+
5000
56+
lfe> (maps:get 'host config "0.0.0.0")
57+
"localhost"
58+
```
59+
60+
When the key is missing you get the default; when it is present the default is ignored and you get the real value. One function — no `try`, no `case`.
61+
62+
There is one wrinkle worth knowing before you sprinkle `maps:get/3` everywhere. LFE, like Erlang, **evaluates arguments eagerly**: every argument is computed *before* the call happens. So the default is built whether or not it ends up being used.
63+
64+
```lfe
65+
lfe> (maps:get 'host config (progn (io:format "computing default~n") "fallback"))
66+
computing default
67+
"localhost"
68+
```
69+
70+
The key `host` is present, so `"fallback"` is thrown away — but `computing default` still printed, because the argument was evaluated first. For a constant like `5000` that costs nothing. For a default that hits a database or spawns a process, it is a real bill, paid on every lookup, including the hits. When the default is expensive, don't reach for `get/3` — reach for `maps:find/2` and decide what to do only on a miss.
71+
72+
```lfe
73+
lfe> (maps:find 'host config)
74+
#(ok "localhost")
75+
lfe> (maps:find 'timeout config)
76+
error
77+
```
78+
79+
`maps:find/2` is `get/3`'s quieter sibling: instead of a default, it tells you whether the key was there at all, as a tagged result — `#(ok Value)` or the bare atom `error` — that you can match on, rather than a value you can't tell apart from a real stored default.
80+
81+
This is also where the decade shows. `dict` has `fetch/2` (crashes, like `get/2`) and `find/2` (tagged, like `maps:find/2`) — but no ergonomic fetch-with-a-default. `maps:get/3` — which arrived in OTP 17.1, a point release behind the `maps` module itself — folds that whole pattern into a single call, and underneath, a map lookup is effectively constant-time for the sizes you'll meet in practice. That economy is a good part of why `maps` won.
82+
83+
Next week we will start putting things *into* maps.

0 commit comments

Comments
 (0)