Tour of Tova
This is a fast-paced tour of the Tova language. Tova is a general-purpose language -- you can use it for scripting, CLI tools, data processing, AI integration, and full-stack web development. Each section introduces a concept with a short code example. By the end, you will have seen every major feature.
1. Variables
Bindings are immutable by default. Use var for mutable variables. Destructure objects and arrays directly with pattern syntax.
x = 5 // immutable
var y = 10 // mutable
y += 1 // OK
{ name, age } = user // destructure an object
[a, b, c] = items // destructure an array2. Functions
Functions are declared with fn. The last expression is the return value.
fn greet(name) {
"Hello, {name}!"
}
greet("Alice") // "Hello, Alice!"Lambdas use the same keyword without a name:
double = fn(x) x * 2Functions can have default parameters:
fn connect(host, port = 8080) {
print("Connecting to {host}:{port}")
}
connect("localhost") // port defaults to 8080
connect("localhost", 3000) // port is 30003. Control Flow
Tova uses if/elif/else -- there is no else if:
fn classify(score) {
if score >= 90 {
"A"
} elif score >= 80 {
"B"
} elif score >= 70 {
"C"
} else {
"F"
}
}Loops with for and while, plus break and continue:
for item in items {
if item == "skip" { continue }
if item == "stop" { break }
print(item)
}
var n = 1
while n <= 10 {
print(n)
n += 1
}Guard clauses for early exits:
fn process(data) {
guard data != nil else { return Err("no data") }
guard len(data) > 0 else { return Err("empty") }
Ok(transform(data))
}4. Pattern Matching
match is one of the most powerful features in Tova. It supports literals, ranges, variant destructuring, wildcards, and guards.
fn describe(value) {
match value {
0 => "zero"
1..10 => "small"
n if n > 100 => "big: {n}"
_ => "other"
}
}Match on custom type variants:
fn area(shape) {
match shape {
Circle(r) => 3.14159 * r * r
Rect(w, h) => w * h
Triangle(b, h) => 0.5 * b * h
}
}Match on arrays and strings:
match list {
[] => "empty"
[x] => "one element: {x}"
[x, y] => "two: {x}, {y}"
_ => "many elements"
}
match path {
"/api" ++ rest => handle_api(rest)
"/static" ++ _ => serve_static(path)
_ => not_found()
}The compiler warns you if you forget to handle a case.
Try "Pattern Matching" in Playground5. Types
Define record types and algebraic data types (ADTs):
type User {
id: Int
name: String
email: String
}
user = User(1, "Alice", "alice@example.com")
print(user.name) // "Alice"Use named arguments for clarity — fields can be in any order:
user = User(name: "Alice", id: 1, email: "alice@example.com")ADTs with variants:
type Color {
Red
Green
Blue
Custom(r: Int, g: Int, b: Int)
}
bg = Custom(30, 60, 90)Tova has built-in generic types Option<T> and Result<T, E>:
val = Some(42) // Option: Some(T) or None
res = Ok("hello") // Result: Ok(T) or Err(E)6. Collections
Arrays, objects, spread, slicing, and comprehensions:
// Arrays
nums = [1, 2, 3, 4, 5]
head = nums[0]
part = nums[1:4] // [2, 3, 4]
// Spread
combined = [...nums, 6, 7]
// Objects
config = { host: "localhost", port: 8080 }
// Comprehensions
evens = [x * 2 for x in range(10)]
squares = [x * x for x in range(10) if x > 0]7. Error Handling
Tova uses Result and Option types instead of exceptions:
fn divide(a, b) {
if b == 0 {
Err("division by zero")
} else {
Ok(a / b)
}
}
result = divide(10, 3)
match result {
Ok(value) => print("Result: {value}")
Err(msg) => print("Error: {msg}")
}Chain operations with .map() and .unwrap():
value = divide(10, 2)
|> .map(fn(x) x * 2)
|> .unwrap()
print(value) // 10Propagate errors with ?:
fn load_config(path) {
content = read_file(path)? // returns Err early if read fails
config = parse_json(content)? // returns Err early if parse fails
Ok(config)
}8. Pipes
The pipe operator |> chains function calls left to right:
result = data
|> filter(fn(x) x > 0)
|> map(fn(x) x * 2)
|> sum()
// Equivalent to: sum(map(filter(data, fn(x) x > 0), fn(x) x * 2))Use _ as a placeholder when the value should not go in the first position:
"hello" |> replace(_, "l", "r") // "herro"9. Modules
Import and publish declarations between files:
// lib/math.tova
pub fn square(x) { x * x }
pub fn cube(x) { x * x * x }
pub TAU = 6.28318// app.tova
import { square, cube, TAU } from "./lib/math"
print(square(5)) // 25
print(TAU) // 6.28318Import npm packages the same way:
import { z } from "zod"
import dayjs from "dayjs"10. Full-Stack Web (Optional)
Everything from sections 1-9 works standalone with tova run my_script.tova -- no server or browser blocks needed. When you want to build a web application, Tova's three-block model lets you write server and browser code in a single .tova file:
shared {
type Message {
id: Int
text: String
author: String
}
}
server {
var messages = []
fn get_messages() {
messages
}
fn post_message(text, author) {
msg = Message(len(messages) + 1, text, author)
messages = [...messages, msg]
msg
}
}
browser {
state messages = []
state draft = ""
state username = "Anonymous"
effect {
messages = server.get_messages()
}
fn send() {
if draft != "" {
server.post_message(draft, username)
draft = ""
messages = server.get_messages()
}
}
component App() {
<div>
<h1>"Chat"</h1>
<ul>
for msg in messages {
<li>
<strong>"{msg.author}: "</strong>
"{msg.text}"
</li>
}
</ul>
<input value={draft} on:input={fn(e) draft = e.target.value} />
<button on:click={send}>"Send"</button>
</div>
}
}Key concepts:
shared-- types and constants available on both server and browser.server-- runs on the server (Bun). Functions here are exposed as RPC endpoints.browser-- runs in the browser. Call server functions withserver.fn_name().state-- reactive signal. When it changes, dependent UI updates automatically.computed-- derived value that recalculates when its dependencies change.effect-- side effect that runs when its dependencies change.component-- a reactive UI component that renders JSX.
Not building for the web?
Everything from lessons 1-9 works standalone with tova run my_script.tova. No server or browser blocks needed. See the I/O guide and CLI Tool example.
CLI Tools
For command-line tools, the cli {} block turns function signatures into a complete CLI interface with argument parsing, validation, and help text:
cli {
name: "todo"
version: "1.0.0"
fn add(task: String, --priority: Int = 3) {
print(green("Added: ") + bold(task))
}
fn list(--all: Bool) {
print("Listing tasks...")
}
}This auto-generates --help, type validation, subcommand routing, and error messages. See the CLI Block guide for the full reference.
11. Async
Tova supports async and await as first-class keywords:
async fn fetch_data(url) {
response = await fetch(url)
data = await response.json()
Ok(data)
}
async fn load_users() {
users = await fetch_data("/api/users")
match users {
Ok(data) => print("Loaded {len(data)} users")
Err(e) => print("Failed: {e}")
}
}12. Interfaces
Define shared behavior across types:
interface Printable {
fn toString() -> String
}
interface Comparable {
fn compare(other) -> Int
}Use derive to auto-implement common interfaces:
type Point {
x: Int
y: Int
} derive [Eq, Show, JSON]This generates equality checking, string representation, and JSON serialization automatically.
That covers the core of Tova. For deeper dives into each topic, continue to the language guide: