Logos.Var (Logos v0.2.0)

Copy Markdown

A Var's identity: %{ns, name}. Conceptually a Var is %{ns, name, value, meta} -- mutable, interned in a Logos.Namespace -- but only the ns/name pair lives on this struct; see below for where value/meta actually live.

Storage-shape decision

value/meta are not fields of this struct -- they live as an ETS row in the owning Logos.Runtime's table ({:var, ns, name} -> %{value:, meta:}, see Logos.Runtime's moduledoc), and every function here (get/2, set!/3, meta/2, alter_meta!/3) is a thin proxy onto Logos.Namespace's ETS accessors, threading a runtime reference in.

Why split it this way rather than making %Logos.Var{} itself carry value/meta fields: a Var is supposed to be a genuine mutable box -- two references to "the same Var" must observe each other's writes. An Elixir struct is immutable data; putting value/meta on the struct would mean every set! requires threading a new struct back to every holder of the old one -- which defeats the whole point: a closure that captured the Var before a later set! would keep seeing the stale struct instead of the new value. The Runtime's :public ETS table is already the one place in this design built for exactly this kind of cross-reference-visible mutation -- it is :public specifically so a process other than the one that created the Var can read/write it directly. So %Logos.Var{ns, name} is intentionally just an identity/address -- a "pointer" -- and all the real mutable state lives in the table it points into. This also means Logos.Var and Logos.Namespace are, by design, not fully independent: Namespace owns the ETS rows, Var is a convenience API over the same rows scoped to one {ns, name} pair. Kept as two modules (rather than folding Var functions directly into Namespace) because call sites that already have a %Logos.Var{} in hand (e.g. a macro-flagged Var found during macroexpansion) read better as Var.get(runtime, var) than Namespace.get_var_value(runtime, var.ns, var.name).

Summary

Functions

Replaces the Var's metadata with fun.(old_meta).

The Var's current value.

Whether this Var is flagged as a macro (meta[:macro] == true) -- the sole mechanism by which defmacro (a bootstrapped ordinary function, see core.logos) makes something behave as a macro.

The Var's current metadata map (%{} if unset).

Builds a Var identity. Does not intern anything -- see Logos.Namespace.intern!/5 for that.

Mutates the Var's value in place.

Types

t()

@type t() :: %Logos.Var{name: String.t(), ns: String.t()}

Functions

alter_meta!(runtime, var, fun)

@spec alter_meta!(Logos.Runtime.t(), t(), (map() -> map())) :: :ok | {:error, term()}

Replaces the Var's metadata with fun.(old_meta).

get(runtime, var)

@spec get(Logos.Runtime.t(), t()) :: {:ok, term()} | :error

The Var's current value.

macro?(runtime, var)

@spec macro?(Logos.Runtime.t(), t()) :: boolean()

Whether this Var is flagged as a macro (meta[:macro] == true) -- the sole mechanism by which defmacro (a bootstrapped ordinary function, see core.logos) makes something behave as a macro.

meta(runtime, var)

@spec meta(Logos.Runtime.t(), t()) :: map()

The Var's current metadata map (%{} if unset).

new(ns, name)

@spec new(String.t(), String.t()) :: t()

Builds a Var identity. Does not intern anything -- see Logos.Namespace.intern!/5 for that.

set!(runtime, var, value)

@spec set!(Logos.Runtime.t(), t(), term()) :: :ok | {:error, term()}

Mutates the Var's value in place.