Functions
Functions are the primary building blocks in Tova. They are declared with the fn keyword and feature implicit returns, optional type annotations, and flexible parameter styles.
Basic Functions
Declare a function with fn, a name, parameters in parentheses, and a body in curly braces:
fn greet(name) {
print("Hello, {name}!")
}
greet("Alice") // Hello, Alice!Implicit Returns
The last expression in a function body is automatically returned. No return keyword needed:
fn add(a, b) {
a + b
}
result = add(3, 4) // 7fn full_name(first, last) {
"{first} {last}"
}
name = full_name("Alice", "Smith") // "Alice Smith"For single-expression functions, this keeps things concise:
fn double(x) {
x * 2
}
fn is_even(n) {
n % 2 == 0
}Explicit Return
Use return when you need to exit a function early:
fn find_first_negative(numbers) {
for n in numbers {
if n < 0 {
return n
}
}
nil
}fn validate_age(age) {
if age < 0 {
return Err("Age cannot be negative")
}
if age > 150 {
return Err("Age seems unrealistic")
}
Ok(age)
}Default Parameters
Parameters can have default values. When a caller omits them, the defaults are used:
fn greet(name, greeting = "Hello") {
"{greeting}, {name}!"
}
greet("Alice") // "Hello, Alice!"
greet("Alice", "Hey") // "Hey, Alice!"fn create_user(name, role = "member", active = true) {
{ name: name, role: role, active: active }
}
create_user("Alice") // { name: "Alice", role: "member", active: true }
create_user("Bob", "admin") // { name: "Bob", role: "admin", active: true }
create_user("Carol", "editor", false) // { name: "Carol", role: "editor", active: false }Type Annotations
Add type annotations to parameters and return types for documentation and type checking:
fn add(a: Int, b: Int) -> Int {
a + b
}
fn greet(name: String) -> String {
"Hello, {name}!"
}
fn is_adult(age: Int) -> Bool {
age >= 18
}The return type annotation uses -> after the parameter list:
fn divide(a: Float, b: Float) -> Result<Float, String> {
if b == 0 {
Err("Division by zero")
} else {
Ok(a / b)
}
}Lambdas (Anonymous Functions)
Tova has two styles for anonymous functions.
fn Lambdas
Use fn(params) body for inline anonymous functions:
double = fn(x) x * 2
add = fn(a, b) a + b
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map(fn(x) x * 2) // [2, 4, 6, 8, 10]
evens = numbers.filter(fn(x) x % 2 == 0) // [2, 4]
sum = numbers.reduce(fn(acc, x) acc + x, 0) // 15For multi-line lambda bodies, use curly braces:
process = fn(item) {
cleaned = item.trim()
validated = validate(cleaned)
validated
}Arrow Syntax
Tova also supports JavaScript-style arrow syntax for lambdas. Both forms are valid Tova; fn(x) expr is the idiomatic style, while x => expr is a shorter alternative:
double = x => x * 2
add = (a, b) => a + b
names = ["alice", "bob", "carol"]
upper_names = names.map(x => x.upper())Implicit it Parameter
When a bare expression references it outside any function, Tova wraps it in a lambda with it as the parameter. This provides a concise shorthand for simple callbacks:
numbers = [1, -2, 3, -4, 5]
positives = numbers.filter(it > 0) // [1, 3, 5]
doubled = numbers.map(it * 2) // [2, -4, 6, -8, 10]This desugars to:
positives = numbers.filter(fn(it) it > 0)
doubled = numbers.map(fn(it) it * 2)Works with pipes:
result = data
|> filter(it > 0)
|> map(it * 2)
|> sorted()TIP
Use it for simple, single-expression callbacks. For multi-line or complex logic, an explicit fn(x) is clearer.
Generator Functions
Functions that use yield become generators. They produce a sequence of values lazily, pausing between each yield:
fn fibonacci() {
var a = 0
var b = 1
loop {
yield a
a, b = b, a + b
}
}
gen = fibonacci()
gen.next() // { value: 0, done: false }
gen.next() // { value: 1, done: false }
gen.next() // { value: 1, done: false }
gen.next() // { value: 2, done: false }Generators compile to JavaScript function* and can be iterated with for...in:
fn count_up(start, end) {
var i = start
while i <= end {
yield i
i += 1
}
}
for n in count_up(1, 5) {
print(n) // 1, 2, 3, 4, 5
}Use yield from to delegate to another generator:
fn combined() {
yield from count_up(1, 3)
yield from count_up(10, 12)
}
// Produces: 1, 2, 3, 10, 11, 12Named Arguments
Named arguments are passed as a single object, so the function should use object destructuring to receive them:
fn create_server({ host, port, debug }) {
print("Starting {host}:{port} debug={debug}")
}
create_server(host: "localhost", port: 8080, debug: true)You can also mix positional and named arguments. The named arguments are grouped into a trailing object:
fn connect(url, { timeout, retries }) {
print("Connecting to {url} timeout={timeout} retries={retries}")
}
connect("https://api.example.com", timeout: 5000, retries: 3)TIP
For type constructors, named arguments behave differently — they are reordered to match the field declaration order instead of being wrapped into an object. See the Types guide for details.
Destructuring Parameters
Functions can destructure objects and arrays directly in the parameter list.
Object Destructuring
fn greet_user({ name, age }) {
"Hello, {name}! You are {age} years old."
}
user = { name: "Alice", age: 30, email: "alice@example.com" }
greet_user(user) // "Hello, Alice! You are 30 years old."fn format_address({ street, city, state, zip }) {
"{street}\n{city}, {state} {zip}"
}Array Destructuring
fn distance([x1, y1], [x2, y2]) {
dx = x2 - x1
dy = y2 - y1
Math.sqrt(dx * dx + dy * dy)
}
distance([0, 0], [3, 4]) // 5.0Async Functions
Prefix a function with async to make it asynchronous. Use await inside to wait for promises:
async fn fetch_user(id) {
response = await fetch("/api/users/{id}")
data = await response.json()
data
}
async fn fetch_all_users() {
users = await fetch_user(1)
print("Got {len(users)} users")
}See the Async guide for more details.
Functions as Values
Functions are first-class values in Tova. You can assign them to variables, pass them as arguments, and return them from other functions:
fn apply_twice(f, x) {
f(f(x))
}
apply_twice(fn(x) x + 1, 5) // 7
apply_twice(fn(x) x * 2, 3) // 12fn make_multiplier(factor) {
fn(x) x * factor
}
triple = make_multiplier(3)
triple(5) // 15
triple(10) // 30Recursive Functions
Functions can call themselves. Tova supports standard recursion:
fn factorial(n) {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
factorial(5) // 120fn fibonacci(n) {
match n {
0 => 0
1 => 1
n => fibonacci(n - 1) + fibonacci(n - 2)
}
}Practical Tips
Keep functions short and focused. A function that does one thing well is easier to test, reuse, and understand.
Lean on implicit returns. Avoid writing return at the end of a function -- just let the last expression be the result. Reserve explicit return for early exits.
Use destructuring parameters when a function operates on a specific shape of data. It makes the function signature self-documenting:
// Instead of:
fn send_email(user) {
to = user.email
name = user.name
// ...
}
// Prefer:
fn send_email({ email, name }) {
// email and name are available directly
}Use decorators for performance-critical code. The @wasm decorator compiles a function to WebAssembly binary, and @fast enables TypedArray optimizations for numeric array parameters. See the Performance guide for details.