Std.Dynamic

View source →

A typed, honest stand-in for a dynamically-shaped value — the well-typed replacement for Any.

Dynamic is a single homogeneous tagged sum over Cure's real BEAM value shapes: an atom, an integer, a float, a string, a list of Dynamic, a tuple (as a list of Dynamic), or a map from Dynamic to Dynamic. Because every leaf is itself a Dynamic, an arbitrarily-nested document — a decoded JSON blob, a heterogeneous config — has one static type, and you narrow to a concrete shape by pattern-matching a constructor rather than by an unchecked runtime is_map/is_tuple guess.

This module knows nothing about optics: it is an ordinary stdlib citizen, usable on its own by anyone who wants a typed Any. Std.Optic imports it to offer per-constructor "case affines" (dyn_str : Affine(Dynamic, String), …), but the dependency is one-way — Std.Dynamic never imports Std.Optic.

Examples

cure
use Std.Dynamic

Int(3)                                   # => Int(3)
match Str("hi")                          # => true
  Str(s) -> true
  other  -> false

Types

  • type Dynamic = Atom | Int | Float | Str | List | Tuple | Map

    A dynamically-shaped value. One constructor per BEAM value shape; the container shapes (List, Tuple, Map) recurse through Dynamic, so the whole thing is one type no matter how deeply nested the data is.

  • type DynamicEntry = Entry

    One key/value entry of a Map. A dynamic map is an association list of these rather than an opaque Map(Dynamic, Dynamic): Dynamic in the key position of an opaque map is not provably strictly positive (an opaque map could be function-backed, making the key contravariant), so the kernel rejects it — exactly as Idris/Agda reject a recursive occurrence through an abstract type. An explicit product whose definition the positivity checker can see is strictly positive in both fields, so the mutual recursion Dynamic/DynamicEntry is accepted.

Functions

  • # fn entries(d: Dynamic) -> List(DynamicEntry)

    The entries of a Map, or the empty list for any other shape. Total: it never crashes on a mis-shaped value, it just yields no entries.

  • # fn entry(k: Dynamic, v: Dynamic) -> DynamicEntry

    Build one association-list entry of a Map.

  • # fn entry_key(e: DynamicEntry) -> Dynamic

    Projections of a DynamicEntry into its key and value halves.

  • # fn entry_value(e: DynamicEntry) -> Dynamic
  • # fn of_atom(a: Atom) -> Dynamic

    Smart constructors: inject a native BEAM value into Dynamic. These are the public way to build a dynamic value — prefer them over naming the raw constructors so callers do not depend on constructor spelling.

  • # fn of_float(x: Float) -> Dynamic
  • # fn of_int(n: Int) -> Dynamic
  • # fn of_list(xs: List(Dynamic)) -> Dynamic
  • # fn of_map(es: List(DynamicEntry)) -> Dynamic
  • # fn of_str(s: String) -> Dynamic
  • # fn of_tuple(xs: List(Dynamic)) -> Dynamic
  • # fn tag(d: Dynamic) -> Atom

    The shape tag of a dynamic value — a total discriminator, one atom per constructor. Use it to branch before narrowing with a match.