Skip to main content

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 if and match.
  • 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.

Prerequisites

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

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.

Editor support

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:

hello.mochi
print("Hello, Mochi!")

Run it:

mochi run hello.mochi
Hello, Mochi!

Three things you did not have to do:

  • No main function. Top-level statements run top to bottom.
  • No imports for print. Common functions come from the prelude.
  • No build step. mochi run compiles, 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.

math.mochi
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:

CommandAction
:helpShow all REPL commands.
:type <expr>Print the inferred type of an expression.
:load <file>Source a .mochi file into the session.
:resetClear all bindings.
:quitExit.

Next