Functions in Vale
Learn how to define and call functions in Vale - parameters, return values, universal function call syntax (UFCS), recursion, and lambdas with Docker-ready examples
Functions are the primary unit of organization in Vale. As an imperative systems language, Vale uses functions the way C, Rust, and C++ do — named blocks of code that take typed parameters and (optionally) produce a typed result. What makes Vale’s functions distinctive is the language design around them: full type inference keeps signatures concise, universal function call syntax (UFCS) lets you call any function with method-style dot syntax, and Vale’s single-ownership memory model gives parameters precise meaning about who owns what.
In this tutorial you will define functions that take parameters and return values, see Vale’s explicit and implicit return styles, use UFCS to call ordinary functions as if they were methods, write a recursive function, and store anonymous functions (lambdas) in variables. Because Vale uses generational references rather than a garbage collector or borrow checker, passing values into functions is simple to write while still being memory-safe at runtime.
Vale infers types throughout, but function signatures still name their parameter and return types — this is the boundary where the compiler checks that callers and callees agree. Every example below is a complete program with its own exported func main() entry point, so you can compile and run each one on its own.
Defining and Calling a Function
A function is declared with the func keyword, followed by the name, a parenthesized parameter list (each parameter written as name Type), and an optional return type. A function with no return type returns nothing.
Create a file named greet.vale:
import stdlib.*;
// A function that takes one parameter and returns nothing
func greet(name str) {
println("Hello, " + name + "!");
}
exported func main() {
greet("Vale");
greet("World");
}
Here greet accepts a single str parameter. The + operator concatenates strings, so "Hello, " + name + "!" builds the full message. The function has no return type, so it simply performs an action (printing) and returns. Running this program prints two greetings, one per call.
Parameters, Return Values, and UFCS
Functions that compute a result declare a return type after the parameter list. Vale supports two return styles: an explicit return statement, or an implicit return where the final expression in the body — written without a trailing semicolon — becomes the result.
Vale also supports universal function call syntax (UFCS): the call x.f(y) is exactly equivalent to f(x, y). This means any ordinary function can be called with method-style dot notation, without the function being defined as a “method” on a type.
Create a file named add.vale:
import stdlib.*;
// Explicit return
func add(a int, b int) int {
return a + b;
}
// Implicit return: the last expression (no semicolon) is the result
func square(x int) int {
x * x
}
exported func main() {
// Normal call syntax
sum = add(3, 4);
print("add(3, 4) = ");
println(sum);
// UFCS: 3.add(4) is the same as add(3, 4)
product = 3.add(4);
print("3.add(4) = ");
println(product);
// UFCS works with single-argument functions too
print("5.square() = ");
println(5.square());
}
Both add and square take and return int. Note that square has no return keyword and no semicolon on its last line — Vale treats that final expression as the return value. The main function calls add two different ways to show that 3.add(4) and add(3, 4) produce the same result. The pattern print(...) (no newline) followed by println(value) lets us label each line of output.
Recursion
Vale functions can call themselves, which is the natural way to express problems defined in terms of smaller subproblems. A classic example is the factorial function. Vale’s if conditions need no parentheses, and the body is enclosed in braces.
Create a file named recursion.vale:
import stdlib.*;
// factorial(n) = n * factorial(n - 1), with factorial(0) = factorial(1) = 1
func factorial(n int) int {
if n <= 1 {
return 1;
}
return n * factorial(n - 1);
}
exported func main() {
print("factorial(5) = ");
println(factorial(5));
}
Each call to factorial either hits the base case (n <= 1, returning 1) or makes a smaller recursive call. The call factorial(5) expands to 5 * 4 * 3 * 2 * 1, which is 120. Because each call gets its own stack frame and its own copy of the parameter n, the recursion is straightforward to reason about.
Lambdas and Scope
Vale supports anonymous functions, called lambdas, written with braces: {(param Type) body}. A lambda is a first-class value, so it can be stored in a local variable and called like any function. Variables — including lambdas — are scoped to the block where they are declared, so a name introduced inside one function is not visible inside another.
Create a file named functions.vale:
import stdlib.*;
// An ordinary top-level function
func double_it(x int) int {
return x * 2;
}
exported func main() {
// `base` is a local variable, visible only inside main
base = 10;
println("Functions in Vale");
// A lambda (anonymous function) stored in a local variable
increment = {(x int) x + 1};
print("increment(base) = ");
println(increment(base));
// A lambda can capture and use surrounding local variables
add_base = {(x int) x + base};
print("add_base(5) = ");
println(add_base(5));
// Ordinary functions can also be invoked with UFCS
print("base.double_it() = ");
println(base.double_it());
}
The lambda {(x int) x + 1} takes one int and returns x + 1; calling increment(base) applies it to 10. The second lambda, add_base, refers to the local variable base from the enclosing scope — lambdas can capture surrounding values. Finally, base.double_it() shows that UFCS applies to top-level functions as well as lambdas, calling double_it(base).
Running with Docker
There is no official Vale image on Docker Hub, so we use the custom codearchaeology/vale:0.2 image (it bundles valec, a JDK for the Scala frontend, and clang for linking).
| |
To run any of the other files, swap the filename — for example mymod=add.vale or mymod=recursion.vale.
Note: The Vale compiler prints verbose build progress (frontend, backend, and linker stages) to stdout before your program runs. The final lines of output are your program’s result. You may also see harmless warnings from Vale’s built-in C support files.
Expected Output
Running functions.vale produces:
Functions in Vale
increment(base) = 11
add_base(5) = 15
base.double_it() = 20
Key Concepts
funcdeclares a function — the signature isfunc name(param Type, ...) ReturnType; omit the return type for functions that return nothing.- Two return styles — use an explicit
return expr;, or let the final expression of the body (written without a semicolon) be the implicit return value. - UFCS unifies calls —
x.f(y)is identical tof(x, y), so any function can be called with method-style dot syntax without being defined as a method. - Recursion is direct — a function may call itself; each call gets its own stack frame and its own copies of the parameters.
- Lambdas are first-class — anonymous functions
{(x Type) body}can be stored in variables, called like functions, and capture surrounding local values. - Local scope — variables (and lambdas) are visible only within the block where they are declared; functions do not share each other’s locals.
- Type inference with checked boundaries — Vale infers types inside function bodies, but parameter and return types in the signature are where the compiler verifies that callers and the function agree.
- Memory safety without ceremony — because Vale uses single ownership plus runtime generational references, passing values to functions stays easy to write while remaining safe.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/vale:0.2
Comments
Loading comments...
Leave a Comment