Mochi quickstart
A fifteen-minute, copy-paste tour of Mochi. By the end you will have installed
mochi, written a handful of programs, defined types, queried a small dataset,
and met the agent and stream constructs the language is built around.
You will:
- Install Mochi and run a one-liner.
- Bind values, write functions, and branch with
ifandmatch. - Define structs and union types.
- Query a list with
from / where / select. - Emit events into an agent and call a generative model.
For a longer walk-through that ends with a working CLI, see the tutorial. For a single condensed page covering every part of the syntax, read language basics.
Mochi runs on macOS (Intel and Apple Silicon), Linux (x86_64 and arm64), and Windows via WSL. See system requirements for the full matrix.
1. Install Mochi
- Binary (recommended)
- Docker
- From source
The install script downloads a static binary for your platform and places it
on your PATH.
curl -fsSL get.mochi-lang.dev | sh
Confirm the install:
mochi --version
mochi run -e 'print("ready")'
mochi 0.10.0
ready
The binary has no runtime dependencies. Copy it between machines and it keeps working.
Mochi ships as ghcr.io/mochilang/mochi. Use it in CI or on hosts where
global tools are off-limits.
docker pull ghcr.io/mochilang/mochi:latest
docker run --rm -i \
-v "$PWD:/work" -w /work \
ghcr.io/mochilang/mochi run hello.mochi
A shell alias keeps the syntax familiar:
alias mochi='docker run --rm -i -v "$PWD:/work" -w /work ghcr.io/mochilang/mochi'
Build from a clone to hack on the compiler or run an unreleased branch.
git clone https://github.com/mochilang/mochi
cd mochi
make build
sudo install -m 0755 bin/mochi /usr/local/bin/mochi
mochi --version
The make target requires Go 1.21 or newer. make install also fetches Deno
so the TypeScript transpilation tests run.
Syntax highlighting ships in the
mochi-lang VS Code extension.
Vim, Helix, and Neovim users can copy the TextMate grammar from the
editors/ directory
of the main repo.
2. Hello, Mochi
Create hello.mochi with a single line:
print("Hello, Mochi!")
Run it:
mochi run hello.mochi
Hello, Mochi!
Three things you did not have to do:
- No
mainfunction. Top-level statements run top to bottom. - No imports for
print. Common functions come from the prelude. - No build step.
mochi runcompiles, caches, and executes in one shot.
To skip the file entirely, evaluate a one-liner with -e:
mochi run -e 'print(2 + 2 * 5)'
12
3. Variables
Mochi has two binding keywords. let is immutable, var is mutable.
let name = "Mochi"
var count = 0
count = count + 1
print(name, count)
Mochi 1
The compiler infers the type from the right-hand side. Annotate explicitly when you want to be sure:
let answer: int = 42
let pi: float = 3.14159
Lists and maps destructure in a binding:
let [first, second] = [10, 20]
let {"name": user_name, "age": user_age} = {"name": "Ada", "age": 36}
print(first + second, user_name, user_age)
30 Ada 36
A let binding cannot be reassigned. The compiler catches the mistake at
compile time. See the errors page for the full
diagnostic shape.
4. Functions
Functions are declared with fun. Parameters are typed and return types are
explicit.
fun add(a: int, b: int): int {
return a + b
}
print(add(2, 3))
5
A single-expression function takes the arrow form. Functions are first-class
values, so you can store them in let bindings or pass them around.
let square = fun(x: int): int => x * x
let twice = fun(f: fun(int): int, x: int): int => f(f(x))
print(square(7))
print(twice(square, 3))
49
81
Recursion works as expected. Mochi does not perform tail-call optimization, so write loops when the depth is unbounded.
fun fact(n: int): int {
if n <= 1 { return 1 }
return n * fact(n - 1)
}
print(fact(6))
720
5. Control flow
if is both a statement and an expression. The expression form uses then
and else.
let n = 17
if n % 2 == 0 {
print("even")
} else {
print("odd")
}
let label = if n > 0 then "positive" else "non-positive"
print(label)
odd
positive
Loops use for ... in over ranges or collections. break and continue
behave the same way as in Python or Go.
for i in 1..4 {
print(i)
}
var total = 0
for x in [10, 20, 30] {
total = total + x
}
print("total =", total)
1
2
3
total = 60
match branches on shape:
fun describe(value: int): string {
return match value {
0 => "zero",
1 => "one",
_ => "many"
}
}
print(describe(0))
print(describe(99))
zero
many
6. Types
Define data with type. Field access is dotted, and constructors use brace
syntax.
type Point {
x: float
y: float
}
let origin = Point { x: 0.0, y: 0.0 }
let target = Point { x: 3.0, y: 4.0 }
fun distance(a: Point, b: Point): float {
let dx = a.x - b.x
let dy = a.y - b.y
return (dx * dx + dy * dy) ** 0.5
}
print(distance(origin, target))
5
Union types are written with |. They model values that take one of several
shapes.
type Shape =
Circle(radius: float)
| Square(side: float)
| Triangle(base: float, height: float)
fun area(s: Shape): float {
return match s {
Circle(r) => 3.14159 * r * r,
Square(s) => s * s,
Triangle(b, h) => 0.5 * b * h
}
}
print(area(Circle(2.0)))
print(area(Square(3.0)))
12.56636
9
The optional/nullable case is a union with nil:
fun safe_div(a: int, b: int): int | nil {
if b == 0 { return nil }
return a / b
}
print(safe_div(10, 2))
print(safe_div(10, 0))
7. Collections
Lists, maps, and sets ship in the prelude. They use familiar literal syntax.
let nums = [1, 2, 3, 4, 5]
let user = {"name": "Ada", "age": 36}
let tags = {"go", "ai", "scripting"}
print(nums[2])
print(user["name"])
print("ai" in tags)
3
Ada
true
Pipelines compose map, filter, and reduce from the prelude:
let evens = nums | filter(fun(n: int): bool => n % 2 == 0)
let squared = evens | map(fun(n: int): int => n * n)
let total = squared | reduce(0, fun(acc: int, n: int): int => acc + n)
print(total)
20
8. Tests
test blocks live in the same file as the code they cover. expect is the
sole assertion and reports the failing expression with line numbers.
fun add(a: int, b: int): int {
return a + b
}
test "add is commutative" {
expect add(2, 3) == add(3, 2)
}
test "add identities" {
expect add(0, 7) == 7
expect add(7, 0) == 7
}
mochi test math.mochi
2 tests passed
There is no separate runner, no fixture framework, and no if __name__ == "__main__" dance. Run mochi test over a file or a directory.
9. Datasets
Mochi treats lists like databases. The from / where / select query form
filters, sorts, and shapes data without leaving the language.
type Product {
name: string
price: int
}
let products = [
Product { name: "Laptop", price: 1500 },
Product { name: "Phone", price: 900 },
Product { name: "Tablet", price: 600 },
Product { name: "Monitor", price: 300 },
Product { name: "Mouse", price: 50 }
]
let top = from p in products
where p.price >= 100
sort by -p.price
take 3
select { name: p.name, price: p.price }
for item in top {
print(item.name, "::", item.price)
}
Laptop :: 1500
Phone :: 900
Tablet :: 600
Swap the literal list for load and the same query reads from a file:
let products = load "products.json" as Product
load understands CSV, JSON, JSONL, and YAML. save writes the same set
back. See datasets for the full surface.
10. Streams and agents
Mochi has reactive concepts in the language itself. A stream is a typed
event channel. An agent holds state and reacts to events with on
handlers, optionally exposing intent endpoints to the outside world.
stream Message { from: string, body: string }
agent inbox {
var unread: int = 0
on Message as m {
unread = unread + 1
print("new from " + m.from)
}
intent count(): int {
return unread
}
}
let box = inbox {}
emit Message { from: "ada", body: "hi" }
emit Message { from: "lin", body: "hey" }
print("unread =", box.count())
new from ada
new from lin
unread = 2
Intents are also how Mochi exposes tools to MCP-aware language models. See agents and streams for a full walk-through.
11. Generative AI
generate text calls a language model. Configure providers with a model
declaration once, then refer to them by name.
model fast {
provider: "openai"
name: "gpt-5.5-mini"
temperature: 0.3
}
let summary = generate text {
model: "fast"
prompt: "Explain bytecode in two sentences."
}
print(summary)
Add as json for structured output, or pass tools: [...] to let the model
call your Mochi functions during generation. See generative
AI for the full block syntax.
12. The REPL
mochi repl opens an interactive session. Each line is type-checked the way
a file would be.
mochi repl
Mochi 0.10.0 :help for commands, :quit to exit
>>> let xs = [1, 2, 3]
>>> xs | map(fun(n: int): int => n * 10)
[10, 20, 30]
>>> :type xs
list<int>
REPL commands:
| Command | Action |
|---|---|
:help | Show all REPL commands. |
:type <expr> | Print the inferred type of an expression. |
:load <file> | Source a .mochi file into the session. |
:reset | Clear all bindings. |
:quit | Exit. |
Next
- Tutorial builds a complete CLI app with types, tests, and packaging.
- Language basics covers every part of the syntax on one page.
- Agents and streams cover reactive design.
- Generative AI covers model calls.
- Datasets covers
from / where / select, joins, and file I/O. - Reference is the concept-by-concept index.
- Common errors indexes frequent compile-time messages and how to fix them.