Access protocol and composable lenses, modelled on Elixir's Access behaviour.

The module has four layers:

  1. A proto Access(C) protocol with fetch/2, get_and_update/3, and pop/2 callbacks. Implementations are provided for plain maps (which also covers records, since Cure records compile to BEAM maps with a __struct__ discriminator) and for keyword-style lists of %[key, value] pairs.
  2. Direct helpers (fetch/2, fetch_bang/2, get/3, get_and_update/3, pop/2) that wrap the protocol callbacks.
  3. An Accessor ADT with factory functions (key, key_default, key_bang, elem_at, at, all, filter) that serve as composable lenses, analogous to Elixir's Access.key/2, Access.at/1, Access.all/0, and Access.filter/1.
  4. Nested traversal helpers (fetch_in/2, get_in/2, put_in/3, update_in/3, get_and_update_in/3, pop_in/2) that accept a list of Accessor values and walk nested data structures.

Examples

use Std.Access

let m = Std.Map.from_list([%[:user, Std.Map.from_list([%[:name, "Ada"]])]])
get_in(m, [key(:user), key(:name)])      # => "Ada"
put_in(m, [key(:user), key(:name)], "Grace")
use Std.Access

# Bump every score in a list of users.
let users = [
  Std.Map.from_list([%[:name, "Ada"],   %[:score, 10]]),
  Std.Map.from_list([%[:name, "Grace"], %[:score, 20]])
]
update_in(users, [all(), key(:score)], fn(s) -> s + 1)

Types

  • type Accessor = AccKey | AccKeyDefault | AccKeyReq | AccTupleElem | AccListAt | AccListAll | AccListFilter

    Tagged union of composable lenses consumed by the nested-traversal helpers (fetch_in, get_in, put_in, ...).

Protocols

  • proto Access(C)

    Key-addressable container protocol: fetch, get_and_update, pop.

Functions

  • # fn __group__() -> Atom

    Group tag consumed by Cure.Stdlib.Preload.

  • # fn all() -> Accessor

    Traverse every element of a list.

  • # fn at(i: Int) -> Accessor

    0-based list index accessor.

  • # fn elem_at(i: Int) -> Accessor

    0-based tuple element accessor (translated to 1-based for the BEAM).

  • # fn fetch(container: Map(Any, Any), key: Any) -> Option(Any)

    Wrap Std.Map.get/2 in an Option.

  • # fn fetch(container: List(Any), key: Any) -> Option(Any)

    Find key in a keyword-style list.

  • # fn fetch_bang(container: C, key: Any) -> Any

    Like fetch/2, but raises :key_error if the key is missing.

  • # fn fetch_in(container: Any, keys: List(Accessor)) -> Option(Any)

    Walk keys through container; returns Some(leaf) or None() if any intermediate lookup is missing.

  • # fn filter(pred: (Any) -> Bool) -> Accessor

    Traverse every element of a list that satisfies pred.

  • # fn get(container: C, key: Any, default: Any) -> Any

    Fetch the value under key, falling back to default when absent.

  • # fn get_and_update(container: Map(Any, Any), key: Any, f: (Any) -> Any) -> Tuple

    Protocol get_and_update for maps; honours the :pop sentinel.

  • # fn get_and_update(container: List(Any), key: Any, f: (Any) -> Any) -> Tuple

    Protocol get_and_update for keyword lists.

  • # fn get_and_update_in(container: Any, keys: List(Accessor), f: (Any) -> Any) -> Tuple

    The full get-and-update workhorse: f receives the leaf and must return %[got, new_leaf] or the atom :pop. Returns %[got, rebuilt_container].

  • # fn get_in(container: Any, keys: List(Accessor)) -> Any

    Walk keys through container; returns nil for missing paths.

  • # fn key(k: Any) -> Accessor

    Plain key accessor. Missing keys collapse to nil in get_in.

  • # fn key_bang(k: Any) -> Accessor

    Required key accessor. Missing keys raise :key_error.

  • # fn key_default(k: Any, default: Any) -> Accessor

    Key accessor with a default substituted when the key is missing.

  • # fn pop(container: Map(Any, Any), key: Any) -> Tuple

    Protocol pop for maps. Raises on records (struct maps).

  • # fn pop(container: List(Any), key: Any) -> Tuple

    Protocol pop for keyword lists.

  • # fn pop_in(container: Any, keys: List(Accessor)) -> Tuple

    Remove the leaf reached by keys, returning %[popped, rebuilt].

  • # fn put_in(container: Any, keys: List(Accessor), value: Any) -> Any

    Replace the leaf reached by keys with value, rebuilding intermediate containers along the way.

  • # fn update_in(container: Any, keys: List(Accessor), f: (Any) -> Any) -> Any

    Apply f to the leaf reached by keys, rebuilding intermediate containers along the way.