Capy-Bearer Documentation

5. Functions

A function gives a name to a calculation. Parameters describe its input. The declared result type describes its output.

Define and call a function

function greet(name : string) string {
    -> "Hello, " + name
}

function RENDER(request : dval) {
    print(greet("Ada"), "\n")
}

Output

Hello, Ada

The -> expression supplies the function result. Use return when a function must finish early.

Parameters and defaults

Parameters have names and types:

function label(name : string, suffix : string = "!") string {
    -> name + suffix
}

function RENDER(request : dval) {
    print(label("Capy"), "\n")
}

label("Capy") uses the default suffix. Defaults are trailing literals. A direct call can omit them, but a function value keeps the full parameter list.

Capy evaluates arguments once from left to right.

Overloads

Several functions can have the same name when their parameter types differ:

function describe(value : s32) string { -> "integer" }
function describe(value : string) string { -> "string" }

function RENDER(request : dval) {
    print(describe(42), " ", describe("Ada"), "\n")
}

The argument types select the overload. A result type never selects one.

Conversion parameters

A parameter declared with as permits one constructor conversion:

function show(value : as string) {
    print(value, "\n")
}

function RENDER(request : dval) {
    show(42)
}

The call behaves as if it passed string(42). Capy does not chain several conversions.

Function values

A function can be stored and called through a typed value:

function double(value : s32) s32 { -> value * 2 }

function RENDER(request : dval) {
    var operation : function(value : s32) s32 = double
    var answer := operation(21)
    print(answer, "\n")
}

A lambda defines a small function at its point of use:

function RENDER(request : dval) {
    var add_one := function(value : s32) s32 {
        -> value + 1
    }
    print(add_one(41), "\n")
}

A lambda can read captured outer values. Scalar captures copy. Array, dval, and struct captures share identity inside the workspace. A capture binding cannot be reassigned. The closure can mutate shared aggregate content.

function RENDER(request : dval) {
    var offset := 2
    var values := [10]
    var update := function() s64 {
        values.push(20)
        -> offset + length(values)
    }
    print(update(), " ", values[1], "\n")
}

Store a callback in a dynamic value

A dval can store a named function or a closure in the current workspace. Read it with its complete function type:

function add_one(value : s32) s32 { -> value + 1 }

function RENDER(request : dval) {
    var step := s32(2)
    var callbacks := {named: add_one, captured: function(value : s32) s32 { -> value + step }}
    var named : function(value : s32) s32 = callbacks.named
    var captured : function(value : s32) s32 = callbacks.captured
    print(named(1), " ", captured(1), "\n")
}

Output

2 3

The function type must match exactly. A mismatch traps. A callable does not cross a public boundary. BRRB, JSON, YAML, and XML replace it with none. Maps keep keys. Lists keep indexes.

Generic and variadic functions

An any parameter creates a compile-time generic function. Use it when the same operation is valid for several concrete types.

A variadic parameter collects values into a typed array:

function print_all(...values : as string) {
    for value := values {
        print(value)
    }
}

function RENDER(request : dval) {
    print_all("one", " ", "two", "\n")
}

Use ...items to spread an array into a variadic call.