02 — A small program
Previous: Installation and Hello, vibe
日本語版: 02_a_small_program.vibe.md
Hello world tells you the toolchain works. It does not tell you whether the language is worth learning. So before any tour of syntax, here is a program that does something: a calculator over expression trees, which evaluates them, prints them back, and copes with division by zero.
It is about forty lines, and you have not been taught any of it yet. Read it as a whole; the rest of the book fills in the pieces.
enum Expr {
Num(Int);
Add(Expr, Expr);
Mul(Expr, Expr);
Div(Expr, Expr)
}
fn eval(e: Expr) -> Int with Exception {
match e {
Num(n) => n,
Add(a, b) => eval(a) + eval(b),
Mul(a, b) => eval(a) * eval(b),
Div(a, b) => {
let d = eval(b)
if d == 0 {
throw("divide by zero")
} else {
eval(a) / d
}
}
}
}
fn show(e: Expr) -> String {
match e {
Num(n) => Int::to_string(n),
Add(a, b) => "(\{show(a)} + \{show(b)})",
Mul(a, b) => "(\{show(a)} * \{show(b)})",
Div(a, b) => "(\{show(a)} / \{show(b)})"
}
}
fn report(e: Expr) -> String {
handle {
"\{show(e)} = \{eval(e)}"
} with Exception {
Throw(message) => "\{show(e)} failed: \{message}"
}
}
fn main with Console {
println(report(Add(Num(2), Mul(Num(4), Num(10)))))
println(report(Div(Num(84), Num(2))))
println(report(Div(Num(1), Add(Num(3), Num(-3)))))
}
(2 + (4 * 10)) = 42
(84 / 2) = 42
(1 / (3 + -3)) failed: divide by zero
What you just used
A tree, described once. enum Expr lists the four shapes an
expression can have, and Div(Expr, Expr) refers to Expr while
defining it. match then takes the tree apart; if you add a fifth shape
and forget to handle it, the compiler tells you which function is now
incomplete instead of picking a branch at runtime.
→ Structs, enums, and match
A function that admits it can fail. eval is written
-> Int with Exception. The with Exception is not documentation: any
caller either passes the possibility along or deals with it, and the
compiler decides which. report deals with it, so report returns a
plain String — the failure stops there.
→ Effects
`main` says what it is allowed to do. fn main with Console is
permission to write to the terminal, and it is the only thing this
program may do. It cannot read a file or open a socket, because it never
asked to.
→ Capabilities
That last point is the one worth pausing on. In most languages, "this
function prints" and "this function can fail" are facts you discover by
reading the body, or by being surprised in production. Here they are in
the signature, the compiler checks them, and they compose: a function
that calls eval and println needs both.
Make it yours
Two changes that stay small and teach the most:
1. Add `Sub(Expr, Expr)`. Add the variant and compile without
touching anything else — the errors point at every match that now
has a hole.
2. Make division truncate toward zero explicitly, or fail on
negative divisors too. Note that you change eval and nothing about
report — the failure channel was already declared.
Next: Values and functions.