vision
Install

Language reference

Generics & monomorphization

A closed type list lets one verb or one kind work with a small, named set of Vision types. Every version is checked at compile time, then monomorphized — the compiler emits one concrete C function per type and one concrete struct per kind/type pair that is actually constructed.

Every rule below describes the current Vision compiler. The Generics and monomorphization concept guide walks the same surface progressively; this page is the reference, terse and exhaustive. The Values reference documents the concrete types the closed list accepts; the Host & C interop reference covers why a monomorphized verb cannot be exposed on the host ABI.

Closed `which works on` lists

A which works on clause names a closed set of concrete Vision types. The list belongs to the verb's or kind's own declaration; the compiler accepts only type words that Vision already knows, and there is no library-author extensibility. The accepted type words are: numbers (or number), decimals (or decimal), text, bytes (or byte), secret, yes/no, and the name of any described thing the program has declared. which works on does not introduce a type; a clause like which works on numbers or text names two separate concrete types, not a synthetic T. The grammar is a disjunction of type words joined by or or a comma — the same shape the returns clause uses.

At a call site, the compiler walks the argument's concrete type and selects the typelist entry that matches. The selection is unambiguous because the argument already has one settled type when the call is checked. When the argument's type is not in the closed list the call is rejected with which-works-on-mismatch, naming the verb, the argument's type, and the list; a typelist that names a type Vision does not know is rejected at the verb's own declaration with unknown-typesig-type, never silently trimmed.

Monomorphized verbs

A generic verb compiles to one specialized C function per declared type that successfully type-checks. to wrap(item), which works on numbers or text emits two C functions: a number version and a text version. Each version's body is checked independently — check_monomorphize_retype in the checker re-runs process_stmts against the verb's body with the parameter rebound to that one type. A body that adds 1, for example, cannot also claim to work on text just because the number version is valid; if the body fails for any listed type the compiler reports which-works-on-body-mismatch for that typelist entry and skips its specialization, so the running program never sees a body that would not type-check. Only types whose trial reported zero raw diagnostics end up with monovalid = 1 and a real C function.

The emitted C symbol for one specialization is <verb>_<type-word> (built by vis_gen_ident with VIS_ID_MONO_VERB, the same constructor the call site uses). A call routes to that symbol statically — emit_mono_specializations chooses the specialization by the argument types actually passed at that call. There is no runtime type tag, no shared base function, no virtual dispatch. Because the C name comes from a deterministic join, two different generic verbs cannot collide on the same type word.

reference-generics-verb.viscompiler checked
to wrap(item), which works on numbers or text:
    answer "[{item}]"

to begin:
    say wrap(42)
    say wrap("hello")
    say wrap(17)
to wrap(item), which works on numbers or text:
    answer "[{item}]"

to begin:
    say wrap(42)
    say wrap("hello")
    say wrap(17)
The generic verb emits one specialized function per listed type. Each call dispatches to the version that matches the argument's type, with zero runtime machinery.

Generic kinds

A generic kind uses the same which works on clause, but construction picks the type. kind labeled, which works on numbers or text: declares a template; a labeled of numbers and a labeled of text are two distinct concrete constructed shapes. A bare field in the kind (one written without an is <type> tail) takes its type from the element type named at construction; an explicitly typed field keeps its declared type in every concrete shape. The checker records which element types are actually constructed as monovalid on the kind's typelist words; the emitter (emit_kind_specializations) then emits one typedef struct per pair the program actually constructed. A generic kind never constructed emits nothing — the same dead-code elimination as an uncalled generic verb.

Constructing a generic kind at a type not on its list is rejected with generic-kind-type-not-in-list, naming the kind and the off-list type. Constructing a generic kind without an element type is rejected with generic-kind-construction-undecided, with the directive that a <kind> of <type> is the only shape that picks a specialization. The bare a <kind> form is reserved for ordinary, non-generic kinds.

reference-generics-kind.viscompiler checked
kind labeled, which works on numbers or text:
    contents
    label is text

to begin:
    the score is a labeled of numbers
    score.contents is 42
    score.label is "points"

    the greeting is a labeled of text
    greeting.contents is "hello"
    greeting.label is "message"

    say "{score.label}: {score.contents}"
    say "{greeting.label}: {greeting.contents}"
kind labeled, which works on numbers or text:
    contents
    label is text

to begin:
    the score is a labeled of numbers
    score.contents is 42
    score.label is "points"

    the greeting is a labeled of text
    greeting.contents is "hello"
    greeting.label is "message"

    say "{score.label}: {score.contents}"
    say "{greeting.label}: {greeting.contents}"
A generic kind with one bare field (`contents`) and one fixed field (`label is text`) constructs two distinct concrete shapes from one source declaration.

A generic kind is checked the same way a generic verb is — each candidate (kind, type) pair is type-checked as if it were an ordinary non-generic kind. The same field-type matching the verb body receives runs against the field list with the bare field retried for the named element type. Pairs that fail checking do not get emitted; the call site is rejected. The construction site resolves the concrete C struct name <kind>_<type-word> by the same one shared vis_ident_join2_raw join the emitter uses, so a construction site can never name a specialization the emitter did not write.

reference-generics-constructed.viscompiler checked
kind pair, which works on numbers or text:
    head
    tail

to begin:
    the numeric is a pair of numbers
    numeric.head is 42
    numeric.tail is 99

    the spoken is a pair of text
    spoken.head is "alpha"
    spoken.tail is "beta"

    say "numeric: {numeric.head}, {numeric.tail}"
    say "spoken: {spoken.head}, {spoken.tail}"
kind pair, which works on numbers or text:
    head
    tail

to begin:
    the numeric is a pair of numbers
    numeric.head is 42
    numeric.tail is 99

    the spoken is a pair of text
    spoken.head is "alpha"
    spoken.tail is "beta"

    say "numeric: {numeric.head}, {numeric.tail}"
    say "spoken: {spoken.head}, {spoken.tail}"
The same generic `pair` is constructed once as `pair of numbers` and once as `pair of text`; the emitter writes two distinct typedefs and the program sees two unrelated shape values.

Multi-parameter constraints

A generic verb with several parameters constrains them with one shared typelist. Every argument at one call site must resolve to the same concrete type — there is no specialization for "first argument numbers, second argument text." A call like to combine(one, two), which works on numbers or text followed by combine one 3 two 4 is fine because both arguments are numbers; combine one 3 two "hi" is rejected with which-works-on-mixed-args, naming the verb and the offending call. The closed list disjunction covers every parameter of the verb at once — there is no per-parameter typelist and no partial specialization.

A generic verb cannot currently be bound to a callable type. a responder is a way to respond, given a whole called position and then install given (preserve) where preserve is monomorphized is rejected with callable-ref-monomorphized, with the explanation that one shared C function does not exist to take the address of, and the bare reference carries no arguments to route from. Wrap the generic verb in a non-generic concrete verb that calls it with one fixed type, then bind that wrapper to the callable type. The same verb cannot be exposed to the host with this program exposes: validate_host_exposes rejects a monomorphized verb withhost-expose-invalid and the message "<name> is monomorphized with `which works on`; expose a concrete wrapper verb for now." A generic verb's body is itself allowed to use effect markers — >> to logmsg(item), which works on numbers or text: marks the whole declaration effectful the same way it does for an ordinary verb, and the effect carries through to every emitted specialization. A call to a generic verb rides its own line's marker as usual.

Generic verbs vs generic kinds

A generic verb and a generic kind use the same closed-list phrase, but the two compile to different shapes:

  • A generic verb emits one specialized C function per type named in its which works on list whose body type-checks — whether or not the program ever calls that specialization. Every valid type on the list receives a concrete function, and the call site picks one of them by argument type.
  • A generic kind emits one concrete typedef struct per (kind, type) pair the program actually constructs. A type named in the kind's list that no construction site picks emits nothing. The runtime never instantiates a kind for a type no construction asked for.

The asymmetry is structural. A verb call has no choice — the compiler must emit a function for every type the verb could legitimately be called with, because the program does not announce which calls will happen before compilation. A kind construction is an explicit a <kind> of <type> at a specific source location; the compiler sees every site, and only those sites need concrete shapes. The same principle underlies dead-code elimination elsewhere in the compiler: unused templates are not emitted, used ones are.

Accepted types and rejections

The accepted type words are precisely:

  • numbers / number / whole numbers / whole number / whole — 64-bit signed integers. numbers is the canonical word; the others are accepted aliases.
  • decimals / decimal — 64-bit IEEE 754 double (default) or 32-bit float under --float.
  • text — borrowed or owned UTF-8 text.
  • bytes / byte — unsigned 0–255 octets.
  • secret — bounded secret text; see the secrets section.
  • yes/no — booleans.
  • The name of any described thing, enum, or tagged union the program has declared.

The rejected forms, with the diagnostic each surfaces, are:

  • A which works on clause that names a type Vision does not know (a typo, a value instead of a type, an enum name, or a type from a different scope) — unknown-typesig-type, raised at the verb's declaration. The unknown-typesig-type message lists the exact set of words the closed list accepts.
  • A call site argument whose concrete type is not in the verb's closed list — which-works-on-mismatch, raised at the call. The message names the verb, the argument's actual type, and the declared set.
  • A call site that mixes types across the verb's parameters — which-works-on-mixed-args, raised at the call. The message names the verb and the mismatch.
  • A body that fails type-checking for one of the verb's listed types — which-works-on-body-mismatch, raised at the verb's declaration. The message names the verb, the failing type, and the operation that did not work for it.
  • A generic kind construction at a type not on the kind's closed list — generic-kind-type-not-in-list, raised at the construction site.
  • A generic kind construction without an element type — generic-kind-construction-undecided, raised at the construction site. The only constructible form is a <kind> of <type>.
  • A callable type whose bound verb is monomorphized — callable-ref-monomorphized, raised at the bind site.
  • A this program exposes declaration whose verb is monomorphized — host-expose-invalid, raised at the expose declaration. The host-expose-* family covers the rest of the host-ABI rejection cases (fallible verbs, mutating parameters, non-ABI return types).

Current limitations

The compiler's generics surface is deliberately small. The following should be treated as gaps, not as supported behavior:

  • No dynamic dispatch. A monomorphized verb compiles to one specialized C function per type, with no shared base function, no virtual table, and no runtime type tag. This is a deliberate design choice: generic calls have the same cost as non-generic calls, and there is no way to construct a value of "one of these types" that can be passed around and dispatched on later. A program that needs runtime dispatch over a closed set of types must use a tagged union.
  • No generic callable types. A callable type can name one verb. The closed-list phrase is not allowed on a callable type declaration; passing a monomorphized verb through one is rejected with callable-ref-monomorphized. Wrap the generic verb in a non-generic concrete wrapper first.
  • No monomorphized host exposure. this program exposes rejects any verb whose declaration carries a which works on clause. The host ABI requires a single C symbol; monomorphization produces one per type. A monomorphized verb is reached by wrapping it in a non-generic concrete verb and exposing that wrapper.
  • One typelist per declaration. A generic verb has exactly one which works on clause. The verb's returns clause is the same shape: a single closed list the answered type must land in. There is no per-parameter typelist, no partial specialization, no where clause, and no associated types. Each parameter of a generic verb is untyped individually; the constraint is shared across the whole signature.
  • No list-of-T or map-of-T generics. The typelist accepts scalar and described-thing type names only. It does not accept list of X, map of X to Y, or any container of a type variable. A generic kind with a bare field gets its concrete type from the of <type> at construction; that concrete type is one of the words above.
  • No generic-of-generic. A monomorphized verb's body cannot call another monomorphized verb with a type the outer call has not already specialized — the inner call would need its own closed list, and Vision does not chain them. The current compiler does not support a workaround at the language level; cross-type composition requires a non-generic concrete verb in between.
  • No struct of generics. A non-generic kind cannot have a field whose type is "any specialization of a generic kind." The kind checker accepts only concrete type names and described things, the same closed set the typelist accepts. A field of label typed as labeled (without of numbers / of text) is rejected becauselabeled alone names a generic template, not a concrete type.

A historical note on decimal composition

The Tier 1.7 concept guide recorded a finding that adding decimal to both a generic verb's and a generic kind's which works on list and assigning the verb's result to a bare field of the constructed kind exposed a compiler field-type mismatch. The canonical form of that exercise — kind labeled, which works on number or decimal plus to preserve(item), which works on number or decimal plus temperature.contents is preserve(21.5) — compiles and runs cleanly, producing temperature: 21.5. The kind-only form (a labeled of decimals with a direct literal assignment) has always worked. The earlier report is recorded here as context for anyone cross-referencing the campaign history; it is no longer a reproducible gap at the documented program shape.