Maps
Maps (a.k.a. dicts, hashes, associative tables) are the BEAM's go-to key/value store, and the shared task here shows their three core moves: build {"ada" => 36, "grace" => 45}, look up "ada", then return an updated copy with "ada" bumped to 37 without mutating the original. Notice that Erlang, Elixir, LFE, and Gleam are all immutable - "updating" really means producing a fresh map and leaving the old one untouched - while Luerl's Lua tables are genuinely mutable, so it has to copy by hand to mimic the BEAM. Watch too how lookup is modelled: Gleam returns a Result instead of crashing or returning nil, whereas the others lean on the {ok, _}/:error convention or a sentinel.
-module(ages).
-export([run/0]).
run() ->
Ages = #{"ada" => 36, "grace" => 45},
%% Lookup; maps:get/2 crashes if the key is missing (use maps:get/3 for a default).
AdaAge = maps:get("ada", Ages),
%% Return an updated *copy*; Ages is unchanged.
Updated = maps:update("ada", AdaAge + 1, Ages),
io:format("~p~n", [Updated]),
Updated.
%% => #{"ada" => 37, "grace" => 45}Erlang's map literal is #{K => V}; maps:get/2 reads a key and maps:update/3 returns a brand-new map with one key changed, leaving the original Ages binding fully intact.
ages = %{"ada" => 36, "grace" => 45}
# Lookup: ages["ada"] returns the value or nil if absent.
ada_age = ages["ada"]
# Immutable update: Map.update/4 builds a fresh map.
updated = Map.update(ages, "ada", 0, fn age -> age + 1 end)
IO.inspect(updated)
# => %{"ada" => 37, "grace" => 45}Elixir reads a key with the ages["ada"] access syntax and updates with Map.update/4, whose function receives the existing value; ages itself never changes because maps are immutable.
import gleam/io
import gleam/int
import gleam/dict
pub fn main() {
let ages =
dict.from_list([#("ada", 36), #("grace", 45)])
// Lookup returns Result(Int, Nil) -- never nil, never a crash.
let ada_age = case dict.get(ages, "ada") {
Ok(age) -> age
Error(Nil) -> 0
}
// Immutable insert returns a new dict with "ada" replaced.
let updated = dict.insert(ages, "ada", ada_age + 1)
case dict.get(updated, "ada") {
Ok(age) -> io.println("ada is now " <> int.to_string(age))
Error(Nil) -> io.println("ada not found")
}
// prints: ada is now 37
}Gleam uses gleam/dict, and dict.get returns a Result(Int, Nil) so a missing key is a value you must handle in a case rather than a nil or an exception; dict.insert returns a fresh dict.
(defmodule ages
(export (run 0)))
(defun run ()
(let* ((ages (map "ada" 36 "grace" 45))
;; Lookup the current value.
(ada-age (maps:get "ada" ages))
;; maps:update returns an updated *copy*.
(updated (maps:update "ada" (+ ada-age 1) ages)))
(io:format "~p~n" (list updated))
updated))
;; (ages:run) => #M("ada" 37 "grace" 45)LFE builds a map with the (map k v ...) form and calls the same Erlang maps:get/maps:update functions; let* threads each step, and the original ages is left unchanged.
local ages = { ada = 36, grace = 45 }
-- Lookup is plain table indexing (returns nil if absent).
local ada_age = ages.ada
-- Lua tables are MUTABLE, so to update "immutably" we copy first.
local function with_updated(t, key, value)
local copy = {}
for k, v in pairs(t) do copy[k] = v end
copy[key] = value
return copy
end
local updated = with_updated(ages, "ada", ada_age + 1)
print(updated.ada, updated.grace) --> 37 45
print(ages.ada) --> 36 (original untouched)Lua tables are mutable, so the BEAM's immutable update is faked by shallow-copying with a pairs loop before changing the key; lookup is just ages.ada, which yields nil for a missing key.