Function Declaration
Function declarations introduce named, callable units of code. Forge supports both classic and natural syntax forms.
Basic Declaration
Classic Syntax
fn greet(name) {
say "Hello, {name}!"
}
Natural Syntax
define greet(name) {
say "Hello, {name}!"
}
Both forms are semantically identical. The function is bound to the given name in the current scope.
Parameters
Parameters are comma-separated identifiers enclosed in parentheses.
fn add(a, b) {
a + b
}
say add(3, 4) // 7
No Parameters
Functions with no parameters use empty parentheses:
fn hello() {
say "hello"
}
hello()
Default Parameters
Parameters can have default values. Default values are used when the caller does not provide an argument for that position.
fn greet(name, greeting = "Hello") {
say "{greeting}, {name}!"
}
greet("Alice") // "Hello, Alice!"
greet("Bob", "Hi") // "Hi, Bob!"
Default parameters must appear after all required parameters.
Variadic Parameters
Forge does not support variadic parameters (rest parameters). To accept a variable number of arguments, use an array parameter:
fn sum_all(numbers) {
reduce(numbers, 0, fn(acc, n) { acc + n })
}
say sum_all([1, 2, 3, 4]) // 10
Return Type Annotation
An optional return type annotation may follow the parameter list:
fn square(n: int): int {
n * n
}
Type annotations are checked by the type checker when enabled. They do not affect runtime behavior in the interpreter.
Return Values
Implicit Return
The last expression in a function body is its return value. This is the idiomatic way to return values in Forge.
fn double(x) {
x * 2
}
say double(5) // 10
Explicit Return
The return keyword exits the function immediately with a value:
fn abs(x) {
if x < 0 {
return -x
}
x
}
A bare return without a value returns null:
fn log_if_positive(x) {
if x <= 0 {
return
}
say "positive: {x}"
}
See Return, Break, Continue for details.
Function Scope
Functions create a new scope. Variables declared inside a function are not accessible outside it. Functions can access variables from their enclosing scope (closure behavior).
let multiplier = 10
fn scale(x) {
x * multiplier // accesses 'multiplier' from outer scope
}
say scale(5) // 50
Recursion
Functions can call themselves recursively:
fn factorial(n) {
if n <= 1 { return 1 }
n * factorial(n - 1)
}
say factorial(5) // 120
fn fib(n) {
if n <= 1 { return n }
fib(n - 1) + fib(n - 2)
}
say fib(10) // 55
Async Functions
Async functions are declared with async fn (classic) or forge (natural):
async fn fetch_data(url) {
let resp = await http.get(url)
resp.body
}
// Natural syntax
forge fetch_data(url) {
let resp = hold http.get(url)
resp.body
}
Async functions return a future that must be awaited with await / hold. See Async Functions.
Nested Functions
Functions can be declared inside other functions:
fn outer() {
fn inner() {
say "inside"
}
inner()
}
outer() // "inside"
Inner functions have access to the outer function’s scope.
Functions Are Values
Function declarations create values that can be stored in variables, passed as arguments, and returned from other functions:
fn add(a, b) { a + b }
fn sub(a, b) { a - b }
let ops = [add, sub]
say ops[0](10, 3) // 13
say ops[1](10, 3) // 7
Parameter Type Annotations
Parameters can include optional type annotations:
fn add(a: int, b: int): int {
a + b
}
These annotations are informational for the type checker and do not enforce types at runtime in the interpreter.