
<div class="callout callout-attn"><p><strong>Early exploration.</strong> Vary and mutation testing are both early in their development. These guidelines reflect what we have learned so far and will evolve as the language and tooling mature.</p></div>

Varyonic programming is the design style encouraged by Vary's verification-first model. It treats program behaviour as something that should be explicit, typed, observable, and testable under mutation.

**Varyonic programming means writing code so that its decisions are typed, its guarantees are explicit, its effects are isolated, and its behaviour is observable.**

## Why this exists

Conventional code can look correct while hiding its real behaviour inside side effects, implicit error handling, stringly typed state, and broad orchestration functions. Varyonic programming is a response to that failure mode. Programs written this way have semantics that can be directly inspected, tested, and stressed by mutation.

This matters even more for LLM-generated code, which tends to pass conventional tests while hiding mutation-blind behaviour behind mixed concerns.

## Behaviour must be observable

The behaviour of a program should be expressed in returned data, not hidden in logs or side effects.

**Not Varyonic:**

```vary
import process

def check_bad(pages: List[Str]) {
    print("broken links found")
    process.exit(1)
}
```

**Varyonic:**

```vary
data BrokenLink {
    source: Str
    target: Str
}

data CheckReport {
    files_checked: Int
    broken_links: List[BrokenLink]
    error_count: Int
}
```

Tests assert on structured results:

```vary-snippet
observe report.error_count == 1
```

If behaviour cannot be observed through returned values, mutation testing cannot measure it.

## Domain models come first

The structure of the domain should be represented explicitly with `data` and `enum`.

**Not Varyonic:**

```vary
def deploy_bad(mode: Str) {
    if mode == "prod" {
        print("deploying to prod")
    }
}
```

**Varyonic:**

```vary
enum DeployTarget {
    Local
    Preview
    Production
}

data DocPage {
    slug: Str
    title: Str
    category: Str
    order: Int
}
```

Types encode meaning and reduce ambiguity.

## Pure functions hold core logic

Important program behaviour should live in pure functions.

Pure logic includes classification, validation, transformation, planning, and report generation.

```vary
enum ChangeKind {
    Css
    Content
    Template
    StaticAsset
    Unknown
}

def classify_change(path: Str) -> ChangeKind {
    if path.endswith(".css") {
        return ChangeKind.Css
    }
    if path.endswith(".html") {
        return ChangeKind.Template
    }
    if path.endswith(".md") {
        return ChangeKind.Content
    }
    return ChangeKind.Unknown
}
```

Put reasoning in pure functions. Effects should execute decisions, not contain them.

## Separate decisions from effects

Programs should be structured as:

```text
Inputs -> Pure planning -> Execution -> Structured report
```

```vary
data BuildStep {
    source: Str
    output: Str
}

data BuildPlan {
    steps: List[BuildStep]
}

data BuildReport {
    files_built: Int
    errors: Int
}

def plan_build(sources: List[Str]) -> BuildPlan {
    mut steps: List[BuildStep] = []
    for s in sources {
        steps = steps + [BuildStep(s, s + ".out")]
    }
    return BuildPlan(steps)
}

def run_build(plan: BuildPlan) -> BuildReport {
    return BuildReport(len(plan.steps), 0)
}
```

The plan describes what should happen. Execution performs the side effects. Tests verify plan semantics separately from execution behaviour.

## Errors are values

Error handling should use Result-style semantics.

```vary
import fs

data DocSource {
    path: Str
    content: Str
}

def load_doc(path: Str) -> Result[DocSource, Str] {
    if not fs.exists(fs.path(path)) {
        return Err("file not found: " + path)
    }
    let rp = fs.read_path(path).unwrap()
    return Ok(DocSource(path, fs.read_text(rp).unwrap()))
}
```

Usage:

```vary-snippet
def process_doc(path: Str) -> Result[Str, Str] {
    let doc = load_doc(path) ?else return Err("invalid document")
    return Ok(doc.content)
}
```

Errors become explicit branches that tests can evaluate.

## Contracts express semantic guarantees

Contracts describe meaningful invariants.

```vary
data DocPage {
    slug: Str
    title: Str
    category: Str
    order: Int
}

def parse_doc(md: Str) -> DocPage {
    in {
        len(md) > 0
    }
    out (result) {
        result.slug != ""
    }
    return DocPage("intro", "Introduction", "docs", 1)
}
```

Contracts provide validation, documentation, and automatic verification. A mutant that violates a contract is killed without a dedicated test. See [Contracts](/docs/contracts/) for syntax and [Contracts in mutation](/docs/mutation/contracts/) for how they interact with mutation testing.

## Effects are isolated

Side effects should be limited to small execution layers.

Common effects: filesystem, subprocess, HTTP server, logging.

```vary
data DeployPlan {
    target: Str
    source_dir: Str
}

data DeployReport {
    success: Bool
    files_copied: Int
}

def plan_deploy(target: Str) -> DeployPlan {
    return DeployPlan(target, "build/")
}

def run_deploy(plan: DeployPlan) -> DeployReport {
    return DeployReport(True, 0)
}
```

Effects should be thin shells around pure decisions.

## Resource lifetimes are explicit

Use `defer` to control resource cleanup.

```vary-snippet
import process
import fs

def cleanup_example() {
    let dir = process.tempdir() + "/work"
    fs.mkdir(fs.path(dir))
    defer { fs.remove_dir(fs.path(dir)).unwrap() }
}
```

This ensures predictable cleanup and reduces hidden state.

## Finite decision spaces

Use enums for closed decision sets.

```vary
enum PageKind {
    Doc
    Blog
    Landing
    Error
}

pure def page_template(kind: PageKind) -> Str {
    match kind {
        case PageKind.Doc { return "doc.html" }
        case PageKind.Blog { return "blog.html" }
        case PageKind.Landing { return "index.html" }
        case PageKind.Error { return "404.html" }
    }
}
```

Finite decision spaces allow mutation testing to exhaustively explore logic. When a match expression covers every variant, a mutant that swaps one branch for another is detectable.

## Expression-oriented code

Use expression forms when appropriate.

```vary
def pick_title(title: Str?, slug: Str) -> Str {
    let result = if title is not None { title } else { slug }
    return result
}
```

Expression-oriented code reduces procedural scaffolding.

## Constructors enforce invariants

Primary constructors should validate object state.

```vary
class ValidatedPage(slug: Str, title: Str) {
    init {
        if slug == "" { raise ValueError("empty slug") }
    }
}
```

This ensures invalid states cannot exist.

## Interfaces isolate external systems

Interfaces encapsulate external dependencies.

```vary
interface FileSystem {
    def read(self, path: Str) -> Str {
    }
    def write(self, path: Str, content: Str) {
    }
}
```

External volatility stays outside core logic. See [HTTP services](/docs/http/) for how interfaces map to service endpoints.

## Architectural pattern

The canonical Vary program structure is:

```text
CLI -> Load context -> Pure planning -> Execution layer -> Structured report -> CLI prints + exit
```

```vary-snippet
data Link {
    source: Str
    target: Str
}

data CheckPlan {
    pages: List[Str]
    known: List[Str]
}

data CheckReport {
    files_checked: Int
    broken_links: List[Link]
    error_count: Int
}

# Pure planning
def plan_check(pages: List[Str], known: List[Str]) -> CheckPlan {
    return CheckPlan(pages, known)
}

def run_check(plan: CheckPlan) -> CheckReport {
    mut broken: List[Link] = []
    for p in plan.pages {
        if not plan.known.contains(p) {
            broken = broken + [Link("site", p)]
        }
    }
    return CheckReport(len(plan.pages), broken, len(broken))
}

# Thin execution shell
import fs
import process

def main() {
    let pages = fs.list_dir(fs.dir_path("content/").unwrap())
    let known = fs.list_dir(fs.dir_path("build/").unwrap())
    let plan = plan_check(pages, known)
    let report = run_check(plan)

    if report.error_count > 0 {
        print("Found " + str(report.error_count) + " broken links")
        process.exit(1)
    }
    print("All links valid")
}
```

Tests verify plan semantics directly:

```vary-snippet
test "broken link detected" {
    let plan = plan_check(["a.html", "missing.html"], ["a.html"])
    let report = run_check(plan)

    observe report.broken_links.len() == 1
    observe report.files_checked == 2
    observe report.error_count == 1
}

test "all links valid" {
    let plan = plan_check(["a.html", "b.html"], ["a.html", "b.html"])
    let report = run_check(plan)

    observe report.broken_links.len() == 0
    observe report.error_count == 0
}
```

## Varyonic module organization

A typical project separates domain logic from effects:

```text
types.vary          # data and enum definitions
config.vary         # configuration loading
paths.vary          # path resolution

docs_parse.vary     # pure parsing logic
navigation.vary     # pure navigation building

render_plan.vary    # pure: what to render
render_execute.vary # effectful: write files

check_links.vary    # pure: find broken links
check_plan.vary     # pure: plan checks

deploy_plan.vary    # pure: plan deployment
deploy_execute.vary # effectful: run deployment

main.vary           # CLI entry point
```

## Non-Varyonic patterns

The following patterns are discouraged:

| Pattern | Why it hurts |
|---------|-------------|
| Magic strings instead of enums | Mutations to string values go undetected |
| Functions returning `None` when behaviour occurred | Callers cannot distinguish success from failure |
| Side effects mixed with decision logic | Pure logic becomes untestable |
| Logging instead of structured reports | No observable return value to assert on |
| Tests asserting string fragments | Brittle and mutation-blind |
| Implicit error handling | Failures are silently swallowed |
| Large functions that mix planning and execution | Too many concerns to test or mutate cleanly |
| Free-form dictionaries instead of domain models | No type safety, no field-level coverage |

## Varyonic scripting

Scripts are where Varyonic discipline pays off most. A typical script starts small, accumulates side effects, and becomes untestable. The Varyonic approach structures scripts into five phases:

```text
Resolve context → Decode inputs → Build plan → Execute effects → Emit report
```

Each phase is a pure function (except Execute) that returns typed data. Only `main()` calls `system.exit()` or writes to stderr. This makes every phase independently testable and every decision observable under mutation.

### The five phases

| Phase | Responsibility | Pure? | Returns |
|-------|---------------|-------|---------|
| **Resolve** | Locate files, determine paths | Yes | `data` with resolved paths |
| **Decode** | Parse and validate inputs | Yes | `Result[T, ScriptError]` |
| **Plan** | Decide what work to do | Yes | `data` describing work items |
| **Execute** | Perform side effects | No | `Result[Report, ScriptError]` |
| **Report** | Format results for output | Yes | Structured report data |

`main()` is a thin shell that calls each phase, handles errors, and prints results.

### Case study: `generate-test-docs`

The `generate-test-docs` script reads a JSON test inventory, renders Markdown docs through templates, and writes the output files. Here is what the old version looked like and how it was refactored.

**Before (stringly typed, exit-in-helpers):**

```vary-snippet
# Helpers call system.exit() and system.stderr() directly
def load_json(path: Str) -> Json {
    let content = read_file(path)
    if content == "" {
        system.stderr().write("error: cannot read " + path + "\n")
        system.exit(1)
    }
    return Json.parse(content).unwrap()
}

def write_output(path: Str, content: Str) {
    let result = fs.write_text(fs.write_path(path).unwrap(), content)
    if result.is_err() {
        system.stderr().write("error: cannot write " + path + "\n")
        system.exit(1)
    }
}

def main() {
    # Path logic inlined and duplicated
    let base = system.cwd()
    let inv = load_json(base + "/ci-artifacts/test-inventory.json")

    # Template rendering mixed with file writing
    let engine = Template.create(base + "/programs/generate-test-docs/templates")
    let rendered = engine.render("test-inventory.md.peb", inv)
    write_output(base + "/website/content/docs/test-inventory.md", rendered)

    let rendered2 = engine.render("test-matrix.md.peb", inv)
    write_output(base + "/docs/test-matrix.md", rendered2)
    print("Done")
}
```

Problems with this style:

| Problem | Consequence |
|---------|-------------|
| `load_json` and `write_output` call `system.exit()` | Tests cannot exercise error paths without killing the process. |
| No typed domain model | The JSON is passed around as an opaque `Json` value with no validation. |
| Planning and execution are interleaved | You cannot test "what would be generated" without actually writing files. |
| Mutation testing cannot kill mutants in error handling | Errors terminate the process, making mutants invisible. |

**After (five-phase Varyonic structure):**

```vary-snippet
import system
import path
import json
import template

# Phase 0: Domain types
data ScriptPaths {
    repo_root: Str
    template_dir: Str
    inventory_json: Str
}

enum ScriptError {
    Input(path: Str, message: Str)
    Decode(field: Str, message: Str)
    Render(template: Str, message: Str)
    Output(path: Str, message: Str)
}

data Inventory {
    suites: Dict[Str, SuiteTotals]
    totals: InventoryTotals
}

data GenerationTarget {
    template_name: Str
    output_rel_path: Str
}

data GenerationPlan {
    targets: List[GenerationTarget]
}

data GenerationReport {
    files_generated: Int
    output_paths: List[Str]
}

# Phase 1: Resolve context
def resolve_paths() -> ScriptPaths {
    let sd = system.script_dir()
    mut base: Str = system.cwd()
    if sd is not None {
        base = path.normalize(path.join(path.parent(sd), ".."))
    }
    return ScriptPaths(base,
        path.join(base, "programs/generate-test-docs/templates"),
        path.join(base, "ci-artifacts/test-inventory.json"))
}

# Phase 2: Decode inputs
def decode_inventory(inv: Json) -> Result[Inventory, ScriptError] {
    let suites_obj = inv.get_object("suites")
    if suites_obj is None {
        return Err(ScriptError.Decode("suites", "missing required field"))
    }
    # ... validate and decode each field, returning Err on failure
    return Ok(Inventory(suites_dict, totals))
}

# Phase 3: Build plan
def build_plan() -> GenerationPlan {
    return GenerationPlan([
        GenerationTarget("test-inventory.md.peb", "website/content/docs/test-inventory.md"),
        GenerationTarget("test-matrix.md.peb", "docs/test-matrix.md")
    ])
}

# Phase 4: Execute effects
def run_generation(plan: GenerationPlan, paths: ScriptPaths,
                   inventory: Json) -> Result[GenerationReport, ScriptError] {
    let engine = template.create(paths.template_dir)
    mut generated: Int = 0
    mut output_paths: List[Str] = []
    for target in plan.targets {
        # render + write, returning Err on failure
        generated = generated + 1
        output_paths = output_paths + [target.output_rel_path]
    }
    return Ok(GenerationReport(generated, output_paths))
}

# Phase 5: main (the only place with exit/stderr)
def main() {
    let paths = resolve_paths()
    let inv = load_inventory_json(paths.inventory_json)
    # ... match on Err, write to stderr, exit(1)
    let decoded = decode_inventory(inv.unwrap())
    # ... match on Err, write to stderr, exit(1)
    let plan = build_plan()
    let report = run_generation(plan, paths, inv.unwrap())
    # ... match on Err, write to stderr, exit(1)
    print(f"Generated {report.unwrap().files_generated} files")
}
```

### What the phases enable

**Testability.** Each phase is a function that takes data and returns data. Tests call `resolve_paths()`, `decode_inventory()`, `build_plan()`, and `run_generation()` directly, with no mocking and no filesystem setup for the pure phases:

```vary-snippet
test "decode rejects missing suites" {
    let inv = Json.parse("{\"totals\": {\"total_files\": 1, \"total_test_cases\": 2}}")
    let result = decode_inventory(inv.unwrap())
    observe result.is_err()
}

test "plan contains expected targets" {
    let plan = build_plan()
    observe plan.targets.len() == 2
}
```

**Mutation resistance.** Because every decision returns a value, mutation testing can kill mutants by asserting on that value. In the old style, a mutant that changes `system.exit(1)` to `system.exit(0)` is invisible to tests because the process dies either way. In the new style, a mutant that changes `ScriptError.Decode` to `ScriptError.Input` is caught by a test that matches on the error variant.

**LLM-assisted maintenance.** An LLM modifying this script can:

| Action | Benefit |
|--------|---------|
| Add a `ScriptError` variant | The compiler flags every `match` that needs updating |
| Add a `GenerationTarget` | The execution loop stays unchanged |
| Add checks to `decode_inventory` | Pure function with clear inputs and outputs |

The phase boundaries act as natural seams. An LLM does not need to understand the whole script to safely modify one phase.

## Further reading

| Page | Topic |
|------|-------|
| [Designing for mutation](/docs/mutation/design-guide/) | How to apply these principles to raise mutation strength |
| [Golden path](/docs/mutation/workflow/) | The mutation testing workflow |
| [Oracles](/docs/mutation/oracles/) | Writing effective test assertions |
| [Contracts in mutation](/docs/mutation/contracts/) | Contracts and mutation testing |
| [Contracts](/docs/contracts/) | Contract syntax reference |
| [HTTP services](/docs/http/) | Interfaces and service endpoints |

## Summary

Program semantics should be explicit, typed, and observable. The language provides tools for this: domain modelling (`data`, `enum`), contracts (`in`, `out`, `old`), pure logic (`pure def`), structured error flows (`Result`, `?else`), observable tests (`observe`), and mutation analysis (`vary mutate`). When these tools work together, you can see whether the program is correct by reading it, testing it, and mutating it.
