Map and Record
import * as MapOps from '@stopcock/fp/map'import * as Record from '@stopcock/fp/record'Reading a key
Section titled “Reading a key”Three lookups, three different answers to “what if it is not there?”:
MapOps.get('ada')(scores) // Option<number>MapOps.getOrUndefined('ada')(scores) // number | undefinedMapOps.getOrElse('ada', () => 0)(scores) // numbergetOrElse is lazy. The fallback runs at most once, and only when the key is
genuinely absent, so an expensive default costs nothing on a hit:
const config = MapOps.getOrElse(url, () => parseExpensiveDefault())(cache)A key whose stored value is undefined is present. All three operations agree
on that: get returns Some(undefined), getOrUndefined returns undefined,
and getOrElse returns undefined without calling the fallback. If you need
to tell “missing” from “stored undefined”, use get.
Every lookup curries the key first, so it drops straight into pipe:
pipe( scores, MapOps.getOrElse('ada', () => 0),)Record or Object?
Section titled “Record or Object?”Both hold string- and symbol-keyed data, so the split is about what the value means, not about the runtime shape.
Reach for Record when the keys are data and every value has the same type: a
lookup table, a counter, an index built at runtime. It is the cheaper contract:
it works in enumerable own properties on null-prototype objects and does not
carry descriptor, accessor, or prototype machinery.
Reach for Object when the keys are the shape: a struct with a known set of
differently-typed fields, especially when you need nested path writes, symbol
preservation, or descriptor-faithful clones.
There is no Record path helper. Nested writes into homogeneous data are
Obj.setPath’s job, and a Record-flavoured copy of it would be the same
traversal with a narrower type.