Tamale.Coord (tamale v0.1.0)

Copy Markdown

Exact rational coordinates — the kernel's only number.

Coordinates appear wherever an anchor references a position rather than an identity: Anchor.Metric intervals, Anchor.Relative offsets, Op.Retime spans, and every Tamale.Warp endpoint. The kernel interpolates and composes those values (Warp.at/2, Warp.compose/2), so the coordinate type must be closed under scaling by rationals — a requirement integers and floats both fail: integers cannot represent frame 3 stretched by 4/3, and floats smear it into rounding dust that no conformance vector can pin.

The kernel's answer is the rational pair {num, den}, normalized so that den > 0 and gcd(|num|, den) == 1. Normalization makes the representation canonical: two equal coordinates are always ==-equal structs of the same shape, so plain term equality is exact equality.

Boundary rules

  • Integers are accepted everywhere and promoted to {n, 1}.
  • Floats are rejected. cast/1 reports them as {:error, {:invalid_coordinate, value}}; cast!/1 raises. This is the same doctrine as the canonical digest (docs/decisions/0005): normalizing domain floats into exact values — seconds as microseconds, frames as integers — is the channel adapter's job, with a declared resolution.
  • Kernel outputs are always normalized rationals.

Wire form

In conformance vectors and any JSON interchange a coordinate is:

  • a JSON integer when den == 14 means {4, 1}
  • a "num/den" string otherwise — "4/3" means {4, 3}

Floats are not representable on the wire; decode/1 rejects them.

Summary

Types

Anything cast/1 accepts: a rational or a bare integer.

t()

A normalized rational: {num, den} with den > 0 and the fraction fully reduced.

Functions

Casts external data to a coordinate. Accepts integers and {num, den} pairs; anything else — floats included — is {:error, {:invalid_coordinate, value}}.

Raising variant of cast/1, for hand-written coordinates.

Decodes the wire form. Floats and malformed strings are {:error, {:invalid_coordinate, value}}.

Divides a by b. Raises ArgumentError when b is zero.

Encodes a coordinate to its wire form: integer, or "num/den".

Promotes an integer to a coordinate.

Builds a normalized coordinate. Raises ArgumentError on a zero denominator or non-integer parts — a caller bug, not data.

Types

input()

@type input() :: t() | integer()

Anything cast/1 accepts: a rational or a bare integer.

t()

@type t() :: {integer(), pos_integer()}

A normalized rational: {num, den} with den > 0 and the fraction fully reduced.

Functions

add(arg1, arg2)

@spec add(t(), t()) :: t()

cast(n)

@spec cast(term()) :: {:ok, t()} | {:error, {:invalid_coordinate, term()}}

Casts external data to a coordinate. Accepts integers and {num, den} pairs; anything else — floats included — is {:error, {:invalid_coordinate, value}}.

cast!(term)

@spec cast!(term()) :: t()

Raising variant of cast/1, for hand-written coordinates.

compare(arg1, arg2)

@spec compare(t(), t()) :: :lt | :eq | :gt

decode(n)

@spec decode(term()) :: {:ok, t()} | {:error, {:invalid_coordinate, term()}}

Decodes the wire form. Floats and malformed strings are {:error, {:invalid_coordinate, value}}.

divide(arg1, arg2)

@spec divide(t(), t()) :: t()

Divides a by b. Raises ArgumentError when b is zero.

encode(arg)

@spec encode(t()) :: integer() | String.t()

Encodes a coordinate to its wire form: integer, or "num/den".

gt?(a, b)

@spec gt?(t(), t()) :: boolean()

gte?(a, b)

@spec gte?(t(), t()) :: boolean()

lt?(a, b)

@spec lt?(t(), t()) :: boolean()

lte?(a, b)

@spec lte?(t(), t()) :: boolean()

max(a, b)

@spec max(t(), t()) :: t()

min(a, b)

@spec min(t(), t()) :: t()

mul(arg1, arg2)

@spec mul(t(), t()) :: t()

negate(arg)

@spec negate(t()) :: t()

new(n)

@spec new(integer()) :: t()

Promotes an integer to a coordinate.

new(num, den)

@spec new(integer(), integer()) :: t()

Builds a normalized coordinate. Raises ArgumentError on a zero denominator or non-integer parts — a caller bug, not data.

sub(a, b)

@spec sub(t(), t()) :: t()