Concept course

Vary Introduction

Ten short programs that show what Vary is for: typed code, pure boundaries, contracts, and tests strong enough to survive mutation.

For each lesson, copy the code into the filename shown in the terminal, run the command next to the lesson, then paste the output of the program into the answer box and validate it. Your score is saved in this browser, so you can leave and come back later on the same device.

Section 1

Why Vary exists

Readable syntax, explicit types, pure boundaries, and tests strong enough for mutation.

main.vary
def deploy_label(env: Str, region: Str) -> Str { return env + "-" + region } print(deploy_label("prod", "us-east-1"))

Lesson 1 / main.vary

Readable code with visible types

Compose a deploy label from typed parts.

Function parameters carry types and the body is short enough to fit on a postcard. No class wrapper is required to run a top-level program, but type information is still visible at every boundary.

Run vary run main.vary
Expected output
prod-us-east-1
main.vary
data Chunk { label: Str bytes: Int } pure def total_bytes(chunks: List[Chunk]) -> Int { mut sum = 0 for chunk in chunks { sum = sum + chunk.bytes } return sum } let response = [ Chunk("headers", 412), Chunk("body", 2048), Chunk("trailers", 96), ] print("bytes=" + str(total_bytes(response)))

Lesson 2 / main.vary

Separate calculation from effects

Sum response bytes in a pure helper and print at the edge.

pure says a function has no side effects. If you keep the math in the pure helper and put I/O at the edge, the helper becomes trivial to test: nothing to set up, nothing to mock.

Run vary run main.vary
Expected output
bytes=2556
main.vary
pure def backoff_ms(attempt: Int) -> Int { if attempt <= 0 { return 100 } if attempt == 1 { return 200 } if attempt == 2 { return 400 } return 800 } print("backoff=" + str(backoff_ms(2)) + "ms")

Lesson 3 / main.vary

Think about test strength

Compute an exponential backoff so the next lessons have an obvious answer.

Vary's mutation tooling probes whether tests actually notice when the implementation changes. Start with a function whose answer is obvious so the expected behaviour is clear before you read the testing docs.

Run vary run main.vary
Expected output
backoff=400ms

Section 2

Language choices

Compact top-level code, explicit mutation, JVM target, and contract clauses.

main.vary
enum ReleaseKind { PATCH MINOR MAJOR } let release = ReleaseKind.MINOR match release { case ReleaseKind.PATCH { print("release=patch") } case ReleaseKind.MINOR { print("release=minor") } case ReleaseKind.MAJOR { print("release=major") } }

Lesson 4 / main.vary

Model a closed choice

Choose a release path with an enum and a top-level match.

A script does not need a main wrapper. Top-level statements can declare a closed set of variants, choose one, and match it exhaustively. When the program grows, you lift the same code into typed functions and modules without restructuring it.

Run vary run main.vary
Expected output
release=minor
main.vary
let responses: List[Int] = [200, 200, 429, 200, 503, 429, 200] mut throttled = 0 for status in responses { if status == 429 { throttled = throttled + 1 } } print("throttled=" + str(throttled))

Lesson 5 / main.vary

Make mutation explicit

Count rate-limited responses while keeping the response list immutable.

Vary defaults to immutable values. Use mut only where a name actually changes: mutation is easy to spot in review, and the checker gets more to work with everywhere else.

Run vary run main.vary
Expected output
throttled=2
main.vary
data DeployTarget { service: Str runtime: Str arch: Str } let target = DeployTarget("billing-api", "JVM 21", "linux/amd64") print(target.service + " -> " + target.runtime + " on " + target.arch)

Lesson 6 / main.vary

Target the JVM

Describe a deploy target with typed fields.

Vary compiles to JVM bytecode, so deploys ship a JAR and run anywhere a JRE runs. Build and deploy metadata can be modelled with the same typed records used elsewhere in the program.

Run vary run main.vary
Expected output
billing-api -> JVM 21 on linux/amd64
main.vary
pure def first_message(messages: List[Str]) -> Str { in { len(messages) > 0 } out (value) { len(value) > 0 } for message in messages { if len(message) > 0 { return message } } return "unknown" } let errors = ["", "timeout after 30s", "connection refused"] print("first=" + first_message(errors))

Lesson 7 / main.vary

Name assumptions

Return the first non-empty message and declare both ends of the contract.

in is a precondition the caller must satisfy. out is a postcondition the function must establish. Both execute at runtime, so wrong assumptions raise instead of slipping through review.

Run vary run main.vary
Expected output
first=timeout after 30s

Section 3

Workflow

Check before running, write testable code, treat the workflow itself as typed data.

main.vary
data Response { endpoint: Str status: Int } pure def is_2xx(response: Response) -> Bool { return response.status >= 200 and response.status < 300 } let response = Response("/api/users", 200) if is_2xx(response) { print("checked: " + response.endpoint) }

Lesson 8 / main.vary

Check before running

Define a typed record and a pure predicate suitable for vary check.

vary check runs the type checker without executing the program. The faster you can rule out type errors, the less time you spend rediscovering them through a slower runtime feedback loop.

Run vary run main.vary
Expected output
checked: /api/users
main.vary
pure def health(failing: Int) -> Str { if failing == 0 { return "green" } if failing <= 2 { return "amber" } return "red" } let probe_results = [True, True, False, True] mut failing = 0 for ok in probe_results { if not ok { failing = failing + 1 } } print(health(failing))

Lesson 9 / main.vary

Prefer observable outputs

Compute a health verdict in a pure helper that a future test can assert against.

When the meaningful result is the return value of a pure helper, tests can assert against the helper directly without scraping stdout. That is the shape mutation testing rewards.

Run vary run main.vary
Expected output
amber
main.vary
data Stage { name: Str timeout_s: Int } let pipeline = [ Stage("compile", 60), Stage("unit", 30), Stage("integration", 120), Stage("deploy", 90), ] mut budget = 0 for stage in pipeline { budget = budget + stage.timeout_s } print("pipeline budget=" + str(budget) + "s")

Lesson 10 / main.vary

The confidence loop

Sum the timeout budget across a typed list of pipeline stages.

Pipeline stages, timeouts, and similar workflow facts deserve the same typed treatment as your domain data. Once they live in records, you can inspect and compare them like any other value instead of grepping shell scripts.

Run vary run main.vary
Expected output
pipeline budget=300s

Course score

0/10 lessons complete

Validate each lesson output to finish this course.

Next course

Vary Language Tour Twelve runnable lessons covering typed values, functions, data, state, enums, dictionaries, nullable values, modules, and tests. Continue