Functions in vv are first-class citizens. You can define named functions using the
fun name(...) syntax. The function body is closed with the end keyword.
fun add(a, b)
return a + b
end
You can also define anonymous functions (function literals) and assign them to variables:
let multiply = fun(a, b)
return a * b
end
To define a function that calls itself, use the fun rec syntax with a named function. Note that
anonymous functions (function literals) cannot be recursive.
fun rec factorial(n)
if n <= 1
return 1
end
return n * factorial(n - 1)
end
Multiple recursive functions that depend on each other can be defined simultaneously using the also
keyword.
fun rec is_even(n)
if n == 0
return true
end
return is_odd(n - 1)
also is_odd(n)
if n == 0
return false
end
return is_even(n - 1)
end
vv supports Tail Call
Optimization for recursive functions. If the recursive call is the last action
in the function (a tail call), the interpreter will optimize it to avoid consuming additional call stack frames,
preventing stack overflow errors on deep recursion.
The following example demonstrates TCO. Notice that the not() operation happens before the
recursive call, ensuring the recursive call itself remains in the tail position. The recursive helper can also
be defined inside the outer function:
fun is_even(n)
fun rec is_even_tail(n, is_even_acc)
if n == 0
return is_even_acc
end
// The recursive call is the very last operation, so TCO applies.
return is_even_tail(n - 1, not(is_even_acc))
end
return is_even_tail(n, true)
end
In contrast, the following example does not benefit from TCO because the recursive call is not the last operation. The multiplication happens after the recursive call returns:
fun rec factorial(n)
if n <= 1
return 1
end
// TCO does NOT apply here because of the `n * ...` operation.
return n * factorial(n - 1)
end
To make the factorial function benefit from TCO, you can use an accumulator variable to keep the
recursive call in the tail position:
fun factorial(n)
fun rec factorial_tail(n, acc)
if n <= 1
return acc
end
return factorial_tail(n - 1, n * acc)
end
return factorial_tail(n, 1)
end
Functions are called using parentheses, passing arguments inside.
let result = add(5, 3) // 8
The return statement immediately exits the function and evaluates the expression as the function's
return value.
If a function finishes execution without reaching a return statement, it effectively returns a
none record ({ type = "none" }).
Calling a function as a standalone statement silently discards its return value. This is useful when you want to call a function only for its side effects:
import result from "std/result.vv"
fun f()
return result.ok(123)
end
f() // OK: return value is silently discarded
You can also use the try keyword on a function call in a statement. This will propagate any
error result to the caller, while silently discarding the ok value:
import file from "std/fs/file.vv"
let fd = try file.create("out.txt")
try file.write(fd, "hello") // propagates write error; ok value (bytes written) is discarded