Functions

Functions

A function is declared with fn, a parameter list, =>, and a return type.

fn join_path(base: string, name: string) => string {
    return `${base}/${name}`
}

A function that returns nothing omits the arrow and the type:

fn log_line(text: string) {
    io::println(text)
}

Default arguments

A parameter can carry a default, which makes it optional at the call site:

fn connect(host: string, port: number = 8080, secure: bool = false) => bool {
    // ...
}

connect("localhost")
connect("localhost", 9000)
connect("localhost", 9000, true)

Arguments are positional, so a call fills parameters left to right and may stop once the rest have defaults.

Returning

return exits with a value. Every path through a function that declares a return type has to return, and the analyzer reports the ones that do not.

fn first_segment(path: string) => string? {
    let cut = path.index_of("/")
    if cut < 0 {
        return None
    }
    return path.substring(0, cut)
}

Generic functions

A function can take its own type parameters, with bounds written using impl:

[public] fn serialize<T: impl Serialize>(value: T) => JsonValue {
    // ...
}

Methods are generic the same way, independently of any parameters their class declares. Array<T> is generic over its element type, and map adds a second parameter of its own:

[public] fn map<U>(transform: closure(T) => U) => Array<U> {
    // ...
}

Here T comes from the class and U belongs to the method alone.

Functions as values

Functions are first-class values, so a named function can be stored, passed on, and returned.

A function type is written as the return type in parentheses, followed by the argument types in parentheses:

(ReturnType)(ArgType, ArgType)

So a function taking a number and returning a number has the type (number)(number):

fn double(value: number) => number {
    return value * 2
}

let transform: (number)(number) = double
let applied = transform(21)

A function with no arguments is (number)(), and one returning nothing is (void)(number).

This is distinct from a closure type, which is written closure(args) => return and is the type of a closure literal. The two describe the same shape of call but are not the same type, so a parameter written one way expects that form.

Pass a named function wherever its type is expected:

let doubled = numbers.map(double)

Inferring type arguments

Type arguments are usually inferred rather than written. The analyzer infers them from the argument types and from the type the call is expected to produce:

let lengths = names.map(closure(name: string) => number {
    return name.size()
})

U is inferred as number from the closure's return type, so the call needs no explicit <number>. Writing the argument out is still allowed when inference has nothing to work from, or when being explicit reads better.