Documentation / Choices

Choices

Choices make alternatives explicit. A value carries one named variant, optionally with a small record payload.

Pattern matching opens the payload safely. Structural equality works only when every possible payload can support it.

Choose the explanation level: Choice declarations

Choice declarations

A choice defines a value that can take one of several named forms.


    Status ::
        Ready,
        Waiting,
        Failed | message String |,
    ;
    

Ready and Waiting are unit variants.

Failed carries one payload field named message.

A choice is its own type. Another choice with similar variants is still a different type.

A choice forms a nominal tagged union.

Each value contains one declared variant.


    Status ::
        Ready,
        Waiting,
        Failed | message String |,
    ;
    

Variant forms

A unit variant has no payload:


    Ready
    

A payload variant uses a record body:


    Failed | message String |
    

Contract

  • The declaration form is Name :: variants ;.
  • Choice and variant names use PascalCase.
  • A choice must declare at least one variant.
  • Variant names must be unique.
  • A payload record must contain at least one field.
  • Payload field names must be unique.
  • Payload fields require explicit types.
  • Payload fields are immutable.
  • Payload fields do not support defaults.
  • Mutable payload field declarations are invalid.
  • Shorthand payload declarations such as Failed String are invalid.
  • Constructor-style declarations such as Failed(String) are invalid.
  • Recursive choice declarations are deferred.
  • A choice has nominal identity. Matching variants and payload shapes do not make two choices interchangeable.

Generic choices

Choices may introduce declaration-site generic parameters.


    Maybe type A ::
        Some | value A |,
        None,
    ;
    

Generic inference and instance restrictions are documented by Generics.

Choose the explanation level: Variant construction

Variant construction

Create a unit variant without parentheses:


    current = Status::Ready
    

Create a payload variant with its values:


    failure = Status::Failed("network")
    

Named arguments also work:


    failure = Status::Failed(message = "network")
    

Payload values cannot be changed after construction. Match the choice to read them.

Construct choice values through the choice name and variant name.

Unit variants

A unit variant is a value without a constructor call.


    current = Status::Ready
    

This form is invalid:


    -- INVALID: unit variants are not constructor calls
    current = Status::Ready()
    

Payload variants

A payload variant requires constructor parentheses.


    failed = Status::Failed("network")
    named = Status::Failed(message = "network")
    

Argument rules

  • Positional arguments fill payload fields in declaration order.
  • Positional arguments must come before named arguments.
  • A named argument must match a payload field.
  • Each payload field must be supplied exactly once.
  • Payload fields have no defaults.
  • Extra or missing arguments are invalid.
  • Argument values are checked and contextually coerced against field types.

Constructed payload fields are immutable.

Direct payload field mutation is invalid.

Use pattern matching to inspect payload fields.

Choose the explanation level: Payload patterns

Payload patterns

Match a payload variant to use its fields.


    if status is:
        Ready => io.line("ready")
        Failed(message) => io.line(message)
    ;
    

The capture name matches the field name.

Use as when the local name should be clearer:


    Failed(message as error_text) => io.line(error_text)
    

Payload values are immutable. Pattern matching reads them without exposing direct field mutation.

Pattern matching inspects choice payloads.


    if status is:
        Ready => io.line("ready")
        Waiting => io.line("waiting")
        Failed(message) => io.line(message)
    ;
    

Capture contract

  • A payload pattern must include every declared field.
  • Captures stay in declaration order.
  • Each source capture name must exactly match its declared field name.
  • as local_name renames only the arm-local binding.
  • Duplicate local binding names are invalid.
  • Payload captures are immutable arm-local bindings.
  • A valid payload arm covers the complete variant regardless of payload values.

    Response ::
        Pending |
            retry_count Int,
            message String,
        |,
        Complete,
    ;

    if response is:
        Pending(retry_count, message as pending_message) =>
            io.line(pending_message)
        Complete => io.line("done")
    ;
    

Deferred and rejected forms

Direct payload field access is deferred:


    message = response.message
    

Payload field mutation is rejected.

Wildcard payload captures are not supported.

Named assignment is not match syntax:


    Failed(message = text) => io.line(text)
    

Nested payload patterns and nested exhaustiveness are deferred.

See Patterns and exhaustiveness for match-level rules.

Choose the explanation level: Choice equality

Choice equality

Choices can use is and is not when their payloads are equality-safe.


    first = Status::Ready
    second = Status::Ready

    same = first is second
    

Two choice values are equal when they use the same variant and their payload values are equal.

A choice with a collection, struct or another unsupported payload cannot use structural equality. Pattern matching works.

Two values of the same choice type support structural equality when every possible payload field supports equality.


    left = Status::Ready
    right = Status::Ready

    same = left is right
    different = left is not right
    

Structural comparison

Equality compares:

  1. the active variant tag
  2. each payload field for that variant

Two different nominal choice types are never comparable, even when their variants have matching shapes.

A choice cannot be compared with a non-choice value.

Choices support equality and inequality. They do not support ordering operators.

Supported payload equality

  • Int
  • Float
  • Bool
  • Char
  • String
  • choices whose payloads all support equality
  • options whose inner type supports equality

The rule applies after generic choice parameters are replaced by concrete type arguments.

Unsupported payload equality

  • structs
  • collections
  • maps
  • functions
  • fallible result carriers
  • external opaque types

If any variant contains an unsupported payload field, equality for the whole choice type is rejected.

Payload matching remains available regardless of equality support.

The current compiler does not fully implement option payload equality inside choices. Choice variants that carry option payloads may not participate in automatic equality even when the inner type supports it. Stage B closes this gap. See progress matrix for the current implementation status.