OxigenLang Functions

Functions in OxigenLang are first-class values — they can be assigned to variables, passed as arguments, and returned from other functions.

#Defining Functions

#Named Functions

Use the fun keyword followed by a name, parameters, and a body:

Oxigen
fun add(a, b) {
    a + b
}

println(add(3, 4))

#Anonymous Functions

Functions can be created without a name and assigned to variables:

Oxigen
double := fun(x) { x * 2 }
println(double(5))

Anonymous functions are expressions, so they can appear anywhere an expression is expected.

#Parameters

#Untyped Parameters

By default, parameters accept any type:

Oxigen
fun greet(name) {
    println("Hello, " + name + "!")
}

greet("Alice")

#Typed Parameters

Add type annotations to enforce parameter types. The annotation uses the same <type> syntax as variable declarations:

Oxigen
fun add(a <int>, b <int>) {
    a + b
}

add(3, 4)

If a typed parameter receives a value of the wrong type, an error is produced:

Oxigen
fun add(a <int>, b <int>) { a + b }
add(3, "four")

You can mix typed and untyped parameters:

Oxigen
fun format(label, value <int>) {
    label + ": " + str(value)
}

#Default Values

Parameters can have default values using = value after the type annotation. If the caller omits the argument, the default is used:

Oxigen
fun greet(name <str>, greeting <str> = "Hello") {
    println("{greeting}, {name}!")
}

greet("Alice")           // Hello, Alice!
greet("Bob", "Hey")      // Hey, Bob!

Default values work with untyped parameters too:

Oxigen
fun add(a, b = 0) { a + b }
println(add(5))      // 5
println(add(5, 3))   // 8

#Optional Parameters

Mark a parameter as optional with ? after the name. Optional parameters default to None when omitted and skip type checking for None:

Oxigen
fun connect(host <str>, port? <int>) {
    option {
        port == None -> println("Connecting to {host} on default port"),
        println("Connecting to {host}:{port}")
    }
}

connect("localhost")         // port is None
connect("localhost", 8080)   // port is 8080

#Mixing Required, Default, and Optional

All three forms can be combined. Required parameters must come first — the parser enforces this:

Oxigen
fun log(msg <str>, level <str> = "INFO", tag? <str>) {
    option {
        tag == None -> println("[{level}] {msg}"),
        println("[{level}] [{tag}] {msg}")
    }
}

log("server started")                       // [INFO] server started
log("request received", "DEBUG")            // [DEBUG] request received
log("auth failed", "ERROR", "security")     // [ERROR] [security] auth failed

Placing a required parameter after an optional or default parameter is an error:

Oxigen
fun bad(x <str> = "hi", y <int>) { ... }
// error: required parameter 'y' cannot follow optional/default parameters

#Named Arguments

When calling a function, arguments can be passed by name using name=value syntax. This is especially useful with default and optional parameters, since it lets you skip parameters you don't need:

Oxigen
fun connect(host <str>, port <int> = 8080, tls? <bool>) {
    println("host={host} port={port} tls={tls}")
}

connect("example.com")                          // positional only
connect("example.com", tls=True)                // skip port, set tls
connect("example.com", port=443, tls=True)      // named for both

Named arguments can be in any order:

Oxigen
fun point(x <int>, y <int>) {
    println("({x}, {y})")
}

point(y=20, x=10)    // (10, 20)

You can mix positional and named arguments — positional arguments must come first:

Oxigen
fun greet(name <str>, greeting <str> = "Hello") {
    println("{greeting}, {name}!")
}

greet("Alice", greeting="Hey")   // Hey, Alice!

A positional argument after a named argument is an error:

Oxigen
greet(name="Alice", "Hey")
// error: positional argument cannot follow named arguments

Named arguments are validated against the function's parameter names:

Oxigen
greet(unknown="Bob")
// error: unknown parameter name: 'unknown'

#Return Values

Oxigen has no return keyword. A function returns the value of its last expression, and give returns early. Writing return x is a syntax error that points here.

#Implicit Return

The last expression in a function body is automatically returned:

Oxigen
fun square(x) { x * x }
println(square(5))

For multi-statement bodies, the last expression is the return value:

Oxigen
fun process(x) {
    doubled := x * 2
    tripled := x * 3
    doubled + tripled
}
println(process(4))

#Explicit Return with give

Use give to return a value early:

Oxigen
fun abs(x) {
    give -x when x < 0
    x
}

give immediately exits the function with the provided value. It works well with postfix guards for concise early returns:

Oxigen
fun safe_divide(a, b) {
    give None when b == 0
    a / b
}

#Functions as Values

Functions are first-class — they can be stored in variables, passed to other functions, and returned:

#Storing in Variables

Oxigen
add := fun(a, b) { a + b }
sub := fun(a, b) { a - b }
println(add(5, 3))
println(sub(5, 3))

#Passing as Arguments

Oxigen
fun apply(f, x) {
    f(x)
}

double := fun(x) { x * 2 }
println(apply(double, 5))

#Returning Functions

Oxigen
fun make_adder(n) {
    fun(x) { x + n }
}

add5 := make_adder(5)
println(add5(10))

#Closures

Functions capture variables from their enclosing scope. The captured variables remain accessible even after the outer function returns:

Oxigen
fun counter() {
    count := 0
    fun() {
        count++
        count
    }
}

c := counter()
println(c())
println(c())
println(c())

#Scope

Each function call creates a new environment (scope). Variables defined inside a function are local to that call:

Oxigen
x := "outer"

fun test() {
    x := "inner"
    println(x)
}

test()
println(x)

Functions can read variables from outer scopes but create new bindings with :=:

Oxigen
multiplier := 10

fun scale(x) {
    x * multiplier
}

println(scale(5))

#Struct Methods

Functions can be attached to structs using includes blocks. Inside a method, struct fields are accessible directly by name (implicit self):

Oxigen
struct Circle {
    radius <float>
}

Circle includes {
    fun area() { 3.14159 * radius * radius }
    fun scale(factor <float>) { radius = radius * factor }
}

c := Circle(5.0)
println(c.area())
c.scale(2.0)
println(c.area())

See the Structs guide for full details on methods, implicit self, and inheritance.

#Recursion

Functions can call themselves:

Oxigen
fun factorial(n) {
    give 1 when n <= 1
    n * factorial(n - 1)
}

println(factorial(5))

#Summary

FeatureSyntax
Named functionfun name(params) { body }
Anonymous functionfun(params) { body }
Typed parameterfun f(x <int>) { ... }
Default valuefun f(x <int> = 10) { ... }
Optional parameterfun f(x? <int>) { ... }
Named argumentf(name="Alice", age=30)
Implicit returnLast expression in body
Explicit returngive value
Early returngive value when condition
ClosureFunctions capture enclosing variables

See also: