Authoritative source: Tuples, records, and telescopes

Tuples, records, and telescopes

tulam treats positional tuples, named records, constructor payloads, function parameters, and dependent products as instances of one ordered n-ary idea: the telescope.

Positional tuples

value item = {42, True, "answer"};
value number = item.0;

Positions are zero-based. {a,b,c} is one three-field tuple—not a nested pair. Explicit nesting remains different:

{a, {b, c}}

This is a two-field tuple whose second field is another two-field tuple.

Named structural records

value ada = {name = "Ada", age = 36};

function birthday(person:{name:String, age:Int})
    : {name:String, age:Int} =
    person { age = person.age + 1 };

Named record types are structural: compatibility follows fields and row rules, not a nominal declaration identity. Update produces a new value unless the type and effect explicitly describe mutation.

Open record types carry a row tail:

{ name:String, ..r }

A function requiring this type may accept additional fields while preserving the unknown row information.

Nominal record-like data

type Person = name:String * age:Int;
value ada : Person = Person { name = "Ada", age = 36 };

Person has a nominal identity and an implicit constructor. Structural records and nominal data may look similar but answer different compatibility questions.

Dependent fields

A telescope is ordered so a later field can mention an earlier value:

type Buffer(a:Type) =
    length:Nat * contents:Vec(a,length);

Construction checks fields from left to right. Projecting contents substitutes the actual projected length into its type.

This same scoping principle applies to a dependent function’s parameters and arguments. One mechanism therefore handles ordinary tuples and genuine Sigma types without normalizing them into binary pair chains.

Runtime representation

Semantic arity is arbitrary finite. The current LLVM object layout can retain at most 255 fields and must reject a wider runtime tuple explicitly. That is an implementation limit, not a language-level arity bound.

Backends may unbox or scalar-replace a telescope, but reflection, checking, and debugging preserve its semantic field order and explicit nesting.

Field names and layout

Names guide projection and structural compatibility; they do not decide whether the runtime representation is “a record” or “a tuple.” Erased type-only fields produce a stable typed-field-to-runtime-field map rather than changing the meaning of the remaining fields.

Common mistakes

{a,b,c} is not {a,{b,c}}, and a structural record is not automatically a nominal type with similar fields. Treat the 255 retained-field LLVM limit as a provider diagnostic, never as a language arity rule.

Recap

Use positional tuples for position-oriented data, structural records for field-oriented open compatibility, and nominal type declarations when an identity and constructors matter. All share flat n-ary telescope machinery.

Normative details: Language Reference §4.4, §4.6, and §5.9.