Language course

Vary Language Tour

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

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

Values, functions, control flow

Typed values, branching, and iteration.

main.vary
let project: Str = "acme-billing" let major: Int = 2 let minor: Int = 14 print(project + " v" + str(major) + "." + str(minor))

Lesson 1 / main.vary

Values and types

Bind typed values and print a release string.

let binds an immutable value. Type annotations are optional when the right-hand side makes the type obvious, but they cost nothing and they document intent for the next reader.

Run vary run main.vary
Expected output
acme-billing v2.14
main.vary
pure def class_of(status: Int) -> Str { if status >= 500 { return "5xx" } if status >= 400 { return "4xx" } if status >= 200 { return "2xx" } return "other" } print("404 -> " + class_of(404))

Lesson 2 / main.vary

Functions and branches

Classify an HTTP status with a pure helper.

Function parameters are typed and the return type follows ->. pure says a function has no side effects, so its results are safe to cache and trivial to test.

Run vary run main.vary
Expected output
404 -> 4xx
main.vary
let durations_ms: List[Int] = [42, 180, 95, 240, 33] mut slow: Int = 0 for d in durations_ms { if d > 100 { slow = slow + 1 } } print("slow=" + str(slow))

Lesson 3 / main.vary

Lists and loops

Count how many requests exceeded a latency budget.

Lists carry their element type, so List[Int] rejects non-integers at compile time. mut marks a local as reassignable; without it the loop counter would be a compile error.

Run vary run main.vary
Expected output
slow=2

Section 2

Modeling data

Records, classes, enums, and nullable values.

main.vary
data Response { status: Int bytes: Int } let resp = Response(200, 1024) print(str(resp))

Lesson 4 / main.vary

Data types

Define an immutable record and read its generated toString.

data declares an immutable record. The compiler generates equals, hashCode, toString, and a structural copy, so two Response(200, 1024) values compare equal and render as Response(status=200, bytes=1024).

Run vary run main.vary
Expected output
Response(status=200, bytes=1024)
main.vary
class RetryBudget(max: Int) { mut remaining: Int = max def consume(self) -> None { self.remaining = self.remaining - 1 } def left(self) -> Int { return self.remaining } } let budget = RetryBudget(3) budget.consume() print("remaining=" + str(budget.left()))

Lesson 5 / main.vary

Classes and state

Track remaining retries behind a small stateful class.

Classes wrap mutable state behind methods. The primary constructor parameter max is in scope inside the body, and mut fields can be reassigned through self. Vary has no class inheritance; use interfaces when you need polymorphism.

Run vary run main.vary
Expected output
remaining=2
main.vary
enum BuildState { QUEUED RUNNING PASSED FAILED } pure def advance(state: BuildState) -> BuildState { match state { case BuildState.QUEUED { return BuildState.RUNNING } case BuildState.RUNNING { return BuildState.PASSED } case BuildState.PASSED { return BuildState.PASSED } case BuildState.FAILED { return BuildState.FAILED } } } print(str(advance(BuildState.QUEUED)))

Lesson 6 / main.vary

Enums and exhaustive match

Advance a build state machine with exhaustive matching.

Enums are a closed set of named variants. match requires every variant to be handled, so adding a new state forces every transition site to be revisited, which is the point.

Run vary run main.vary
Expected output
RUNNING
main.vary
let configured: Int? = None mut port: Int = 8080 if configured is not None { port = configured } print("port=" + str(port))

Lesson 7 / main.vary

Nullable values

Fall back to a default when a configured value is missing.

A type suffixed with ? accepts None. Before you can treat the value as the underlying type, the checker forces you to handle the missing case. That is how Vary keeps null-deref out of the language.

Run vary run main.vary
Expected output
port=8080

Section 3

Lookups and modules

Typed maps and helpers shaped for reuse.

main.vary
let timeouts: Dict[Str, Int] = {"db": 30, "http": 10, "cache": 60} print("http=" + str(timeouts["http"]) + "s")

Lesson 8 / main.vary

Typed maps

Look up a service timeout from a config map.

Dictionaries are typed maps. Dict[Str, Int] requires Str keys and Int values, indexed with dict[key].

Run vary run main.vary
Expected output
http=10s
main.vary
pure def coord(group: Str, name: Str, version: Str) -> Str { return group + ":" + name + ":" + version } print(coord("acme", "billing", "1.4.2"))

Lesson 9 / main.vary

Module-ready helpers

Format an artifact coordinate with a pure helper.

A helper whose inputs arrive as parameters and whose output is a return value has no dependency on top-level state, so it can move into another file and import cleanly into a caller.

Run vary run main.vary
Expected output
acme:billing:1.4.2

Section 4

Tests

Pure helpers, passing assertions, and failure output.

main.vary
pure def class_of(status: Int) -> Str { if status >= 500 { return "5xx" } if status >= 400 { return "4xx" } return "2xx" } print(class_of(503) + "," + class_of(200))

Lesson 10 / main.vary

Shape helpers for assertion

Print the inputs and outputs the next lesson will assert on.

Pure helpers are easy to test because the input is fully visible at the call site. The two prints below preview the cases the next lesson locks in with observe.

Run vary run main.vary
Expected output
5xx,2xx
tests/grade_test.vary
pure def class_of(status: Int) -> Str { if status >= 500 { return "5xx" } if status >= 400 { return "4xx" } return "2xx" } test "server errors classify as 5xx" { observe class_of(503) == "5xx" } test "client errors classify as 4xx" { observe class_of(404) == "4xx" }

Lesson 11 / tests/grade_test.vary

Run passing assertions

Run two observe assertions against the helper.

test "name" blocks live at the top level alongside code. Each observe is an assertion: if the expression is false at runtime, the test fails and the runner exits non-zero.

Run vary test tests/grade_test.vary
Expected output
Results: 2 passed, 0 failed
tests/grade_test.vary
pure def class_of(status: Int) -> Str { if status >= 500 { return "5xx" } if status >= 400 { return "4xx" } return "2xx" } test "server errors classify as 5xx" { observe class_of(503) == "5xx" } test "boundary 500 should be 4xx" { observe class_of(500) == "4xx" }

Lesson 12 / tests/grade_test.vary

Read a failing test

Run a file with one intentional boundary mistake and find the summary line.

An intentionally wrong assertion shows what failure output looks like. The runner exits non-zero but still prints the pass/fail summary, so CI scripts can read both the signal and the detail in one stream.

Run vary test tests/grade_test.vary
Expected output
Results: 1 passed, 1 failed

Course score

0/12 lessons complete

Validate each lesson output to finish this course.

Next course

Vary Testing Seven runnable lessons covering `test`, `observe`, edge cases, error paths, output, and naming. Continue