kex docs Standard Library 0.4.0-alpha kex.run ↗

Mapprelude

type Map<K, V>

An immutable key-value store, written {key: value}.

Keys are compared by structural equality and may be of any type; atom keys get the shorthand {name: "Ada"}, string keys are written out in full as {"name": "Ada"}. Every method answers with a new map: the ! forms (put!, delete!) build a new map and rebind the receiver variable, they do not modify anything in place.

Entries come back in canonical key order, not insertion order, so keys, values, entries and any traversal are stable and comparable across equal maps.

let config = { host: "localhost", port: 8080 }
config.get(:host).or("0.0.0.0")     # => "localhost"
config.get(:user).or("anonymous")   # => "anonymous"
config.put(:port, 9090)             # => { :host: "localhost", :port: 9090 }

A map is Enumerable and Foldable, and the traversal blocks take the key and value as two parameters:

config.each { |k, v| IO.printLine("${k} = ${v}") }
config.filter { |k, v| k != :port }   # => { :host: "localhost" }

Declared for the same reason list.kex declares type List<X> = [X]: it gives the name Map a source declaration, so it resolves as a type through the collected interfaces rather than needing to be known to the compiler.

Variants

  • abstract

make Map<K, V> implements Enumerable, Foldable, Monoid

reduce

Folds over the map's (key, value) pairs in canonical key order.

This is Map's Enumerable primitive: map, filter, find, any? and the rest are built on it. The block receives the accumulator and one pair; destructure the pair to name its halves.

reduce(acc, g)
Parameters
acc A
the initial accumulator
g A -> (K, V) -> A
combines the accumulator with each pair

Returns: A — the final accumulator

Examples

Summing the values

{ a: 1, b: 2 }.reduce(0) do |acc, pair|
  let (key, value) = pair
  acc + value
end
# => 3

Rendering the map as a query string

{ a: 1, b: 2 }.entries.map { |k, v| "${k}=${v}" }.join("&")

combine

Combines two maps by merging them, with other's values winning on a key conflict. The Monoid operation, and the same thing merge does.

combine(other)
Parameters
other This
the map to merge in

Returns: This — the combined map

Examples
{ a: 1 }.combine({ b: 2 })   # => { :a: 1, :b: 2 }

Folding a list of maps into one

[{ a: 1 }, { b: 2 }, { a: 9 }].reduce({}) { |acc, m| acc.combine(m) }
# => { :a: 9, :b: 2 }

get

Returns the value stored under key, or None when the key is absent.

Missing keys are an ordinary answer rather than a failure, so a lookup on data you did not produce is safe by default. Use the two-argument form below when you have a sensible fallback.

get(key) : K -> V?
get(key) : K -> V -> V
Parameters
key K
the key to look up

Returns: V? — the value, or None

Examples
let user = { name: "Alice", age: 32 }
user.get(:name)      # => Just("Alice")
user.get(:missing)   # => None

Chaining through a nested map

settings.get(:server).flatMap { |s| s.get(:port) }.or(8080)

put

Returns a new map with key mapped to value, replacing any previous entry for that key.

The receiver is untouched. Use put! when you want the variable holding the map to be rebound to the result.

put(k, v) : K -> V -> Map<K, V>
Parameters
k K
the key to set
v V
the value to store

Returns: Map<K, V> — a new map including the entry

Examples
{}.put(:x, 1)              # => { :x: 1 }
{ x: 1 }.put(:x, 2)        # => { :x: 2 }

Rebinding with the ! form

var totals = {}
totals.put!(:visits, 1)
totals                     # => { :visits: 1 }

delete

Returns a new map without key. A key that is not present is not an error: the map comes back unchanged.

Use delete! to rebind the receiver variable.

delete(key) : K -> Map<K, V>
Parameters
key K
the key to remove

Returns: Map<K, V> — a new map without that entry

Examples
{ a: 1, b: 2 }.delete(:a)   # => { :b: 2 }
{ a: 1 }.delete(:z)         # => { :a: 1 }

Stripping a secret before logging

IO.printLine(params.delete(:password))

has?

Returns true when key has an entry in the map.

Distinguishes a missing key from one whose value is itself empty, which a get with a default cannot.

has?(key) : K -> Bool
Parameters
key K
the key to test

Returns: Booltrue when the key is present

Examples
{ a: 1 }.has?(:a)   # => true
{ a: 1 }.has?(:z)   # => false

Checking a required setting

if !config.has?(:host)
  IO.printError("host is required")
end

count

Returns the number of entries satisfying pred.

count : (K -> V -> Bool) -> Integer
Parameters
pred K -> V -> Bool
the test applied to each entry

Returns: Integer — how many entries matched

Examples
{ a: 1, b: 2 }.count { |k, v| v > 1 }   # => 1

How many settings are still at their default

config.count { |k, v| v == defaults.get(k, v) }

each

Calls f with each key and value, for its side effects.

each : (K -> V -> Void) -> Void
Parameters
f K -> V -> Void
called once per entry

Returns: Void

Examples
scores.each { |k, v| IO.printLine("${k}: ${v}") }

map

Applies f to each key and value and collects the results into a LIST.

Note the return type: map comes from Enumerable, whose contract is to produce a list, because f may return anything at all. Use mapValues or mapKeys when you want a map back.

map : (K -> V -> R) -> [R]
Parameters
f K -> V -> R
applied to each entry

Returns: [R] — the results, in canonical key order

Examples
{ "a": 1, "b": 2 }.map { |k, v| "${k}=${v}" }   # => ["a=1", "b=2"]

Building a header block

headers.map { |name, value| "${name}: ${value}" }.join("\n")

mapValues

Returns a new map with every value replaced by f(value). The keys are left alone.

mapValues(f) : (V -> W) -> Map<K, W>
Parameters
f V -> W
applied to each value

Returns: Map<K, W> — a map with the same keys and transformed values

Examples
{ a: 1, b: 2 }.mapValues { |v| v * 10 }   # => { :a: 10, :b: 20 }

Normalising values read as text

raw.mapValues { |s| s.trim.lowerCase }

mapKeys

Returns a new map with every key replaced by f(key). The values are left alone.

If f maps two keys onto the same result, one entry wins: the map cannot hold both.

mapKeys(f) : (K -> J) -> Map<J, V>
Parameters
f K -> J
applied to each key

Returns: Map<J, V> — a map with transformed keys

Examples
{ "a": 1, "b": 2 }.mapKeys { |k| k.upperCase }   # => { A: 1, B: 2 }

Making header lookups case-insensitive

headers.mapKeys { |name| name.lowerCase }

filter

Returns a new map with only the entries for which pred answers true.

Map overrides the map-returning HOFs (Enumerable's default returns a list).

filter(pred) : (K -> V -> Bool) -> Map<K, V>
Parameters
pred K -> V -> Bool
the test applied to each entry

Returns: Map<K, V> — the matching entries

Examples
{ a: 1, b: 2, c: 3 }.filter { |k, v| v > 1 }   # => { :b: 2, :c: 3 }

Keeping only the options that were actually set

options.filter { |name, value| !value.blank? }

reject

Returns a new map with the entries for which pred answers true removed. The complement of filter.

reject(pred) : (K -> V -> Bool) -> Map<K, V>
Parameters
pred K -> V -> Bool
the test applied to each entry

Returns: Map<K, V> — the entries that failed the predicate

Examples
{ a: 1, b: 2, c: 3 }.reject { |k, v| v > 1 }   # => { :a: 1 }

Dropping internal keys before serialising

record.reject { |name, _| name.startsWith?("_") }

merge

Returns a new map holding the entries of both. When a key appears in both, other's value wins.

The right-biased rule is what makes this the natural way to apply overrides on top of defaults.

merge(other) : Map<K, V> -> Map<K, V>
Parameters
other Map<K, V>
the map whose values take precedence

Returns: Map<K, V> — the combined map

Examples
{ a: 1, b: 2 }.merge({ b: 99, c: 3 })   # => { :a: 1, :b: 99, :c: 3 }

Layering user settings over defaults

defaults.merge(userConfig)

any?

Returns true when at least one entry satisfies pred. Stops at the first match.

any? : (K -> V -> Bool) -> Bool
Parameters
pred K -> V -> Bool
the test applied to each entry

Returns: Booltrue when any entry matches

Examples
{ a: 1, b: 2 }.any? { |k, v| v > 1 }   # => true
{ a: 1, b: 2 }.any? { |k, v| v > 9 }   # => false

all?

Returns true when every entry satisfies pred. The empty map answers true.

all? : (K -> V -> Bool) -> Bool
Parameters
pred K -> V -> Bool
the test applied to each entry

Returns: Booltrue when all entries match

Examples
{ a: 1, b: 2 }.all? { |k, v| v > 0 }   # => true
{ a: 1, b: 2 }.all? { |k, v| v > 1 }   # => false

Validating a form

fields.all? { |name, value| !value.blank? }

find

Returns the first entry satisfying pred as a (key, value) tuple, or None when nothing matches.

"First" means first in canonical key order.

find : (K -> V -> Bool) -> (K, V)?
Parameters
pred K -> V -> Bool
the test applied to each entry

Returns: (K, V)? — the matching entry, or None

Examples
{ a: 1, b: 2 }.find { |k, v| v > 1 }   # => Just((:b, 2))
{ a: 1, b: 2 }.find { |k, v| v > 9 }   # => None

Locating a value without knowing its key

users.find { |id, user| user.email == target }

make Map<K, V> implements Blankable