# `Schooner.Value`
[🔗](https://github.com/ausimian/schooner/blob/1.0.0/lib/schooner/value.ex#L1)

Term representation of Scheme values.

Where the BEAM already distinguishes types cleanly we use them
directly: booleans, the empty list, cons cells, integers, floats, and
strings (binaries) all live as their native Elixir form. Where types
would otherwise alias — symbols vs strings vs bytevectors (all binaries),
characters vs integers, vectors vs records vs closures (all tuples) —
the colliding side is tagged.

## Representations

  * Booleans  — bare Elixir `true` / `false`. Only `false` is
    Scheme-falsy. Every other value is truthy, including `:null` and `0`.
  * Empty list — bare Elixir `[]`
  * Pair — Elixir cons cell `[car | cdr]`. Improper Scheme pairs are
    improper Erlang lists (`[a | b]` where `b` is not a list).
  * Symbol — `{:sym, binary}` (binaries, not atoms; user-supplied
    identifiers must not exhaust the BEAM atom table)
  * String — bare Elixir binary. Disambiguated from symbol/bytevector
    because those remain tagged.
  * Character — `{:char, codepoint}`
  * Exact integer — bare Elixir integer
  * Exact rational — `{:rational, numerator, denominator}`. Always
    reduced (`gcd(num, denom) == 1`), denominator strictly greater
    than 1, sign carried on the numerator. A rational whose
    denominator would reduce to 1 is collapsed to its bare integer
    form by `rational/2`, so the integer/rational split is total —
    no Schooner value is both.
  * Inexact real — bare Elixir float
  * Non-finite inexact — `{:float_special, :pos_inf | :neg_inf | :nan}`
    (BEAM floats cannot represent these; tagged forms carry IEEE-754
    semantics through arithmetic and comparison without smuggling
    forbidden bit patterns into Elixir floats.)
  * Complex — `{:complex, real, imag}` where each component is any
    non-complex number (integer, rational, float, or float_special).
    `complex/2` collapses to the bare real component when `imag === 0`
    so the real/complex split is total — any value with a complex tag
    has a non-zero (or inexact) imaginary part. Polar literals are
    converted to rectangular form on read.
  * Vector — `{:vector, tuple}`
  * Bytevector — `{:bytevector, binary}`
  * Closure — `{:closure, params, body, env, name_or_nil}`
  * Primitive — `{:primitive, name, arity, fun}`
  * Record — `{:record, type_id, fields_tuple}`
  * Record type identity — `{:record_type, name, unique_int}` —
    embedded as a literal in the bindings produced by
    `define-record-type`. Self-evaluating; compared with `===` so
    two definitions with the same record name in different
    lexical scopes produce distinct identities.
  * Error object — `{:error_obj, kind, message, irritants}` where
    `kind` is `:user | :read | :file`, `message` is a Scheme string
    value, and `irritants` is an Elixir list of Scheme values
    (rendered as a Scheme list by the accessor). Constructed by
    `(error msg irritant ...)` and reachable via `error?`,
    `error-object?`, `read-error?`, `file-error?`.
  * Parameter — `{:parameter, id, init, converter}`. `id` is a
    process-monotonic unique integer used as the lookup key in
    the per-process dynamic-binding stack. `init` is
    the post-converter initial value (returned when no
    `parameterize` is currently shadowing the parameter).
    `converter` is either `nil` or a Scheme procedure applied to
    every value installed for the parameter (initial *or* via
    `parameterize`). Parameters are procedure values: calling one
    with zero arguments returns the current value.
  * Foreign — `{:foreign, term}`. Opaque host payload: any Elixir
    term wrapped so Scheme code can pass it around (bind, return,
    stash in a pair/vector) but never inspect or forge it. Built by
    the host via `foreign/1`; the wrapped term is read back from
    Elixir with `foreign_ref/1`. Scheme code can ask `(foreign? x)`
    to discriminate, but has no constructor and no accessor, and
    `write` redacts the contents to `#<foreign>` — so the wrapped
    term remains structurally invisible from Scheme. Identity
    equality only: `eq?` / `eqv?` / `equal?` compare the wrapped
    terms with `===`, so two foreigns that wrap structurally-equal-
    but-distinct host values are not equal.
  * EOF — `:eof`
  * Unspecified — `:unspecified`

# `arity_spec`

```elixir
@type arity_spec() ::
  non_neg_integer()
  | {:at_least, non_neg_integer()}
  | {:between, non_neg_integer(), non_neg_integer()}
```

# `bool_v`

```elixir
@type bool_v() :: boolean()
```

# `bytevector_v`

```elixir
@type bytevector_v() :: {:bytevector, binary()}
```

# `char_v`

```elixir
@type char_v() :: {:char, non_neg_integer()}
```

# `closure_v`

```elixir
@type closure_v() :: {:closure, term(), term(), term(), binary() | nil}
```

# `complex_v`

```elixir
@type complex_v() :: {:complex, real_v(), real_v()}
```

# `error_kind`

```elixir
@type error_kind() :: :user | :read | :file
```

# `error_obj_v`

```elixir
@type error_obj_v() :: {:error_obj, error_kind(), t(), [t()]}
```

# `float_special_v`

```elixir
@type float_special_v() :: {:float_special, :pos_inf | :neg_inf | :nan}
```

# `foreign_v`

```elixir
@type foreign_v() :: {:foreign, term()}
```

# `pair_v`

```elixir
@type pair_v() :: nonempty_maybe_improper_list(t(), t())
```

# `parameter_v`

```elixir
@type parameter_v() :: {:parameter, integer(), t(), t() | nil}
```

# `primitive_v`

```elixir
@type primitive_v() :: {:primitive, binary(), arity_spec(), (list() -&gt; t())}
```

# `promise_v`

```elixir
@type promise_v() :: {:promise, :forced, t()} | {:promise, :lazy, term()}
```

# `rational_v`

```elixir
@type rational_v() :: {:rational, integer(), pos_integer()}
```

# `real_v`

```elixir
@type real_v() :: integer() | rational_v() | float() | float_special_v()
```

# `record_type_id_v`

```elixir
@type record_type_id_v() :: {:record_type, binary(), pos_integer()}
```

# `record_v`

```elixir
@type record_v() :: {:record, term(), tuple()}
```

# `string_v`

```elixir
@type string_v() :: binary()
```

# `sym_v`

```elixir
@type sym_v() :: {:sym, binary()}
```

# `t`

```elixir
@type t() ::
  bool_v()
  | []
  | pair_v()
  | sym_v()
  | string_v()
  | char_v()
  | integer()
  | rational_v()
  | float()
  | float_special_v()
  | complex_v()
  | vector_v()
  | bytevector_v()
  | closure_v()
  | primitive_v()
  | record_v()
  | record_type_id_v()
  | error_obj_v()
  | promise_v()
  | parameter_v()
  | foreign_v()
  | :eof
  | :unspecified
```

# `vector_v`

```elixir
@type vector_v() :: {:vector, tuple()}
```

# `bool`

```elixir
@spec bool(boolean()) :: bool_v()
```

# `boolean?`

```elixir
@spec boolean?(term()) :: boolean()
```

# `bytevector`

```elixir
@spec bytevector([byte()] | binary()) :: bytevector_v()
```

# `bytevector?`

```elixir
@spec bytevector?(term()) :: boolean()
```

# `char`

```elixir
@spec char(non_neg_integer()) :: char_v()
```

# `char?`

```elixir
@spec char?(term()) :: boolean()
```

# `closure`

```elixir
@spec closure(term(), term(), term(), binary() | nil) :: closure_v()
```

# `complex`

```elixir
@spec complex(real_v(), real_v()) :: real_v() | complex_v()
```

Build a rectangular complex value. `real` and `imag` must be
non-complex numbers (integers, rationals, floats, or `:float_special`
sentinels). When `imag` is the exact integer `0` the result collapses
to `real`, keeping the real / complex split total: a value tagged
`:complex` always carries a non-zero (or inexact) imaginary part.

Inexact zeros (`+0.0`, `-0.0`) do **not** collapse — `(make-rectangular
1.0 0.0)` is `1.0+0.0i`, distinct from `1.0`. Per r7rs §6.2.6 only an
exactly zero imaginary part makes the whole number `real?`.

# `complex?`

```elixir
@spec complex?(term()) :: boolean()
```

Scheme `complex?`. Schooner ships the full numeric tower from
integers up through complex, so `complex?` and `number?` agree.

# `display`

```elixir
@spec display(t()) :: binary()
```

Human-readable rendering. Strings render without quotes and characters as
their literal codepoint. Aggregates recurse using `display`-style rendering
for their elements.

# `display_iodata`

```elixir
@spec display_iodata(t()) :: iodata()
```

# `eager_promise`

```elixir
@spec eager_promise(t()) :: promise_v()
```

Wrap `value` as an already-forced (eager) promise. Forcing it
returns `value` without invoking any procedure — the same shape as
r7rs's `(make-promise obj)`.

# `eof?`

```elixir
@spec eof?(term()) :: boolean()
```

# `eq?`

```elixir
@spec eq?(t(), t()) :: boolean()
```

Scheme `eq?`. Identical to `eqv?` for Schooner — r7rs permits an
implementation to collapse the two so long as the stronger discriminations
`eq?` is allowed to draw (distinct heap copies of structurally-equal
aggregates) are also drawn by `eqv?`. They are: see `eqv?/2` below.

# `equal?`

```elixir
@spec equal?(t(), t()) :: boolean()
```

Scheme `equal?`. Recurses structurally into pairs, vectors, bytevectors,
strings, and records. For everything else it falls through to `eqv?`.

# `eqv?`

```elixir
@spec eqv?(t(), t()) :: boolean()
```

Scheme `eqv?`. Atomic values (numbers, characters, symbols, booleans,
`()`, `:eof`, `:unspecified`) compare by content, with exactness preserved
for numbers — `(eqv? 1 1.0)` is `#f`. Aggregates (pairs, vectors, strings,
bytevectors, records, procedures, promises, parameters, error objects)
compare by physical identity via `erts_debug.same/2` — two
structurally-equal aggregates built independently are *not* `eqv?`. This
is r7rs's "denote different locations in the store" semantics, recovered
for an immutable value model by leaning on the BEAM's heap layout instead
of explicit identity tags.

`erts_debug.same/2` is an unofficial OTP BIF; it has been stable across
many releases and is used by the Erlang/OTP test suite. Compiler / loader
literal sharing means two source-level literals like `'(1 2)` written in
the same module *may* be deduplicated and report `same` — r7rs explicitly
permits constants to be shared, so this is allowed but worth pinning with
a test if relevant to caller behaviour.

# `error_kind?`

```elixir
@spec error_kind?(term(), error_kind()) :: boolean()
```

# `error_object`

```elixir
@spec error_object(error_kind(), t(), [t()]) :: error_obj_v()
```

# `error_object?`

```elixir
@spec error_object?(term()) :: boolean()
```

# `exact?`

```elixir
@spec exact?(term()) :: boolean()
```

# `float_special?`

```elixir
@spec float_special?(term()) :: boolean()
```

# `foreign`

```elixir
@spec foreign(term()) :: foreign_v()
```

Wrap an arbitrary Elixir term as an opaque foreign Scheme value.
Scheme code can bind, store, and pass the result through any
value-shaped position (variables, pairs, vectors, closures) but
cannot inspect, deconstruct, or forge it: there is no surface
syntax that constructs a foreign and `write` redacts the wrapped
term to `#<foreign>`. The host retrieves the wrapped term with
`foreign_ref/1`.

# `foreign?`

```elixir
@spec foreign?(term()) :: boolean()
```

# `foreign_ref`

```elixir
@spec foreign_ref(foreign_v()) :: term()
```

Unwrap a foreign value, returning the host term it wraps. Raises
`ArgumentError` for any non-foreign — this accessor is host-only
and assumes the caller has already established the value is one.

# `improper_list`

```elixir
@spec improper_list([t(), ...], t()) :: t()
```

Build an improper Scheme list. The last argument becomes the cdr of the
innermost pair.

# `inexact?`

```elixir
@spec inexact?(term()) :: boolean()
```

# `integer?`

```elixir
@spec integer?(term()) :: boolean()
```

# `lazy_promise`

```elixir
@spec lazy_promise(term()) :: promise_v()
```

Wrap `thunk` (a zero-arg Scheme procedure) as a lazy promise.
`force` applies the thunk and, if the result is itself a promise,
keeps unwrapping iteratively. Schooner promises do not memoise —
no mutation is available, so repeated forcing re-evaluates.

# `list`

```elixir
@spec list([t()]) :: t()
```

Build a Scheme list (proper, null-terminated) from an Elixir list.

After the pair/null untagging this is the identity on Elixir lists.
Kept as a named constructor so call sites stay self-documenting and
the seam survives any future representation change.

# `list?`

```elixir
@spec list?(term()) :: boolean()
```

Scheme `list?` — true for `[]` and proper (null-terminated) cons chains;
false for improper lists, atoms, and anything that ends in a non-pair non-null.

# `null?`

```elixir
@spec null?(term()) :: boolean()
```

# `number?`

```elixir
@spec number?(term()) :: boolean()
```

# `pair`

```elixir
@spec pair(t(), t()) :: pair_v()
```

# `pair?`

```elixir
@spec pair?(term()) :: boolean()
```

# `parameter`

```elixir
@spec parameter(t(), t() | nil) :: parameter_v()
```

Build a parameter value with a fresh id. `init` is the
already-converted initial value. `converter` is either `nil` (when
`make-parameter` was called without one) or a Scheme procedure
applied to every value installed for the parameter via
`parameterize`.

# `parameter?`

```elixir
@spec parameter?(term()) :: boolean()
```

# `primitive`

```elixir
@spec primitive(binary(), arity_spec(), (list() -&gt; t())) :: primitive_v()
```

# `procedure?`

```elixir
@spec procedure?(term()) :: boolean()
```

# `promise?`

```elixir
@spec promise?(term()) :: boolean()
```

# `rational`

```elixir
@spec rational(integer(), integer()) :: integer() | rational_v()
```

Build a normalised exact rational. Result is always reduced (numerator
and denominator share no common factor), the sign is carried on the
numerator, and a denominator of 1 collapses to a bare integer so
integer-valued rationals are never tagged.

Raises `ArithmeticError` on a zero denominator — callers in primitive
paths should detect division-by-zero earlier and surface a structured
Scheme-level error instead.

# `rational?`

```elixir
@spec rational?(term()) :: boolean()
```

Scheme `rational?`. True for exact integers, exact rationals, and any
finite inexact real (since every finite IEEE-754 double has an exact
rational representation). False for the non-finite specials
(`+inf.0`, `-inf.0`, `+nan.0`) and non-numeric values.

# `real?`

```elixir
@spec real?(term()) :: boolean()
```

Scheme `real?`. Real numbers are anything outside the `:complex` tag —
integers, rationals, finite floats, and the non-finite specials. A
complex value whose imaginary part is exact zero collapses to its
real part on construction, so a tagged complex always has a non-zero
(or inexact) imaginary part and is therefore not real.

# `record`

```elixir
@spec record(term(), tuple()) :: record_v()
```

# `record?`

```elixir
@spec record?(term()) :: boolean()
```

# `string`

```elixir
@spec string(binary()) :: string_v()
```

# `string?`

```elixir
@spec string?(term()) :: boolean()
```

# `symbol`

```elixir
@spec symbol(binary()) :: sym_v()
```

# `symbol?`

```elixir
@spec symbol?(term()) :: boolean()
```

# `to_list`

```elixir
@spec to_list(t()) :: [t()]
```

Convert a proper Scheme list (cons cells terminated by `[]`) into
an Elixir list. Raises `ArgumentError` on an improper list — the
inverse of `list/1`.

# `truthy?`

```elixir
@spec truthy?(t()) :: boolean()
```

Scheme truthiness: only `false` is false; every other value is
truthy, including `:null`, `0`, and the empty string.

# `unspecified?`

```elixir
@spec unspecified?(term()) :: boolean()
```

# `vector`

```elixir
@spec vector([t()]) :: vector_v()
```

# `vector?`

```elixir
@spec vector?(term()) :: boolean()
```

# `write`

```elixir
@spec write(t()) :: binary()
```

Machine-readable rendering. Strings are wrapped in quotes, characters as
`#\name` or `#\x..`, and unprintable characters are escaped.

# `write_iodata`

```elixir
@spec write_iodata(t()) :: iodata()
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
