A tour of the language

Orchard's agent-native parts (gen, delegate, spawn) get the attention, but most of the code you write is ordinary: variables, values, operators, and control flow. This lesson covers that core so the rest of the tutorial reads without surprises.

Comments

Line comments start with //. Block comments use /* ... */ and nest, so you can comment out a region that already contains a block comment.

// a line comment
let x = 1   // they also work at end of line
/* a block comment
   /* that nests */
   and keeps going */

Statements end themselves

You do not write semicolons. Statements terminate at the end of a line, the same way Go does it. The lexer is smart about lines that are obviously unfinished: a newline does not end a statement when it falls inside parentheses or brackets, right after a binary or assignment operator, after , . -> => ? : |> ?? .., after an open brace, or right before else, catch, until, or a closing bracket. So you can break a long expression across lines freely:

let total = price
    + tax
    + shipping        // continues: the line ended on a +

let msg = greeting |>
    capitalize        // continues: the line ended on a pipe

let and var

let binds a value that does not change. var binds one that does. Types are inferred locally, so you rarely annotate, but you can when it helps a reader or resolves ambiguity:

let name = "Ivy"            // str, inferred
let count = 0               // int, inferred
var total: float = 0.0      // annotated, reassignable
total = total + 1.5
var items: list<str> = []   // annotate the empty literal, it is otherwise ambiguous

The value types

The built-in scalar types are:

  • str: text.
  • int: whole numbers.
  • float: floating point numbers.
  • bool: true or false.
  • null: the absence of a value.
  • duration: a span of time, integer-valued, like 30s.
  • money: an amount, like $0.50.
  • bytes: raw binary data.
  • json: the dynamic supertype; any value fits in a json.

And the composite types:

  • list<T>: an ordered list of T, like list<str>.
  • map<K, V>: a mapping from keys to values, like map<str, int>.
  • T?: an optional T: either a T or null. str? is a string that might be absent.

You also declare your own record types (type) and enumerations (enum), which the typed generation lesson leans on.

Literals

Each type has a literal form:

let big   = 1_000          // underscores group digits for readability
let pi    = 3.14
let huge  = 1e9            // scientific notation is a float
let flag  = true
let none  = null
let wait  = 30s            // durations: 30s 15m 2h 1d
let price = $0.50          // money
let names = ["Ada", "Lin", "Mo"]      // list<str>
let ages  = { "Ada": 31, "Lin": 29 }  // map<str, int>

Durations are integer-valued and written with a unit suffix: 30s, 15m, 2h, 1d. Money is written with a leading $. Both have natural JSON forms when they cross into generation: money lowers to "$d.dd" and a duration to a string like "30m".

Strings and interpolation

Strings use double quotes. Inside one, { expr } splices the value of an expression in. Write {{ and }} for literal braces. The usual escapes work: \n, \t, \", \\.

let user = "Ada"
let n = 3
let line = "Hi {user}, you have {n} messages."
let math = "1 + 1 = {1 + 1}"             // interpolation holds any expression

Triple-quoted strings span multiple lines and are ideal for prompts and instructions. They interpolate the same way:

let prompt = """
    Summarize the report below for {user}.
    Keep it under three sentences.
"""

Operators and precedence

The boolean operators are the words and, or, and not. Comparisons are == != < <= > >=. Arithmetic is + - * / %. Two more pull their weight constantly:

  • ?? is null-coalescing: a ?? b is a unless a is null, in which case it is b. It is how you give an optional a default.
  • |> is the pipe: x |> f(2) is exactly f(x, 2). It threads a value through a chain of calls left to right.
  • .. and ..= build ranges (exclusive and inclusive of the end), used most often in for loops.

Precedence runs from loose to tight: or, then and, then the comparisons, then ranges, then the pipe, then + -, then * / %, then not and unary minus, then ??, then the postfix forms (. field access, ?. optional access, [] indexing, () calls). When in doubt, parenthesize.

let name = maybe_name ?? "anonymous"            // default an optional
let title = topic |> trim() |> capitalize()    // read left to right
let ok = score >= 50 and not banned

Labeled arguments

Functions, tools, and skills can be called with labeled arguments, where you name the parameter at the call site. This reads clearly and frees you from remembering argument order:

post_to_slack(channel: "#oncall", text: "deploy finished")

Lambdas

A lambda is an inline function written (x) => expr. They are most useful with list operations and the pipe:

let nums = [1, 2, 3, 4]
let doubled = nums |> map((n) => n * 2)        // [2, 4, 6, 8]
let names = users |> map((u) => u.name)

Control flow

if / else work as you expect. Note that a brace block is also an expression: its value is its trailing expression, so an if can produce a value.

if score >= 90 {
    emit "excellent"
} else if score >= 50 {
    emit "passing"
} else {
    emit "needs work"
}

let grade = if score >= 50 { "pass" } else { "fail" }   // if as an expression

for ... in iterates over a list or a range. while loops while a condition holds. repeat n runs a block a fixed number of times. break and continue work inside all of them.

for name in names {
    emit "hello {name}"
}

for i in 0..3 {            // 0, 1, 2  (exclusive end)
    emit "step {i}"
}

var n = 5
while n > 0 {
    n = n - 1
}

repeat 3 {
    emit "tick"
}

match chooses a branch by pattern. Over an enum the checker proves your match is exhaustive, so you cannot forget a case (the typed generation lesson makes heavy use of this). A _ branch catches the rest:

match status {
    "active"   => emit "running"
    "paused"   => emit "on hold"
    _          => emit "unknown"
}

For errors, throw raises one and try / catch handles it. A throw carries a value you can inspect in the catch:

try {
    let v = risky_step()
    emit "ok: {v}"
} catch (e) {
    emit "failed: {e}"
}
Note. Because a block is an expression, you can write let x = { ... trailing expr } and bind the block's result. This is why the budget and retry forms in later lessons can produce a value.

Next. With the core syntax in hand, move to typed generation, the feature that makes the model return values of the types you just learned to declare.