Functions
Functions must be declared at the top level. A function sees its own
parameters and locals, constants, and shared variables of the
module, but not the program body's variables:
func multiply(int left, int right) int do
return left * right
endThe return type is optional and defaults to void:
func greet() do
print("hello")
return
endCalls to functions defined later in the file work because the generated C
contains forward prototypes. Arguments must match the parameters' types, and
each parameter without a default needs one. A void function may use a bare return; any other function must
return a value of its type on every path. The compiler checks this simply: the
function's last statement must be a return, an if with an else whose
branches all end that way, or a match that handles every value and
whose branches all end that way.
func sign(int x) int do
if x < 0 then
return -1
else if x == 0 then
return 0
else
return 1
end
endDefaults and named arguments
A parameter can have a default, after its name. A call may then leave it out, and any argument can be given by its parameter's name, after those given by position:
func connect(str host, int port = 80, bool tls = false) str do
return f"{host}:{port} tls={tls}"
end
connect("example.com") // example.com:80 tls=false
connect("example.com", 8080)
connect("example.com", tls: true)
connect(port: 443, host: "example.com", tls: true)Once a parameter has a default, those after it need one too. A default is evaluated at each call that leaves it out, after the values the call gives, so each call gets its own array or map; like a field's default, it may use constants and call functions, but not variables or other parameters. Methods take defaults and named arguments the same way. A function value is called with all its arguments, by position.
Overloading and nested function declarations are not supported; a function written as an expression, below, covers most uses of nested functions.
Functions as values
A function is a value like any other. Its type lists the types of its
parameters and its result, as in func(int, int) bool, or func(str) for one
that returns nothing. func() int[] returns an array; an array of
functions puts the function type in parentheses, as in
(func() int)[] steps. A named function can be passed by its name, and a
function can be written as an expression with => before its result:
func twice(int n) int do
return n * 2
end
func apply(func(int) int f, int value) int do
return f(value)
end
print(apply(twice, 5)) // 10
print(apply(func(int n) => n * n, 5)) // 25Function values can be stored in variables, arrays, maps, and struct fields,
returned from functions, and called with parentheses, as in
handlers["save"](file). A function written as an expression may use the
variables around it; their values are copied when the function is made, so
a later change to the variable does not reach it:
func adder(int amount) func(int) int do
return func(int n) => n + amount
end
func(int) int add_ten = adder(10)
print(add_ten(5)) // 15The parameter types can be left out where the function goes somewhere that
says them, as in sort_by(words, func(a, b) => len(a) < len(b)). The result
type can be written before =>: func(int n) f64 => n / 2.0. Function
values cannot be compared or printed, and are never null.
A function written as an expression can also hold statements, between do
and end, and return its result with return:
str[] labels = map(scores, func(int score) do
if score >= 90 then
return "excellent"
end
return "score " + to_str(score)
end)Its result type is the one written before do, or else the one the place
it goes expects, as sort_by expects a bool, or else the type of its first
return with a value; without any, it returns nothing. Every path must then
return a value of that type. ? works in it when its result type is a
Result or an Option, written or expected. It sees the variables around it
as a function written with => does; break and continue there belong to
its own loops. An error inside it leaves out the whole statement that holds
it when more errors are reported.
Shared variables
A function written as an expression uses the variables around it as they are when it runs, and can change them, as closures do in most languages:
int clicks = 0
func() on_click = func() do
clicks += 1
end
on_click()
on_click()
print(clicks) // 2
func counter() func() int do
int n = 0
return func() do
n += 1
return n
end
endA variable that such a function uses, and that is assigned anywhere, in the
function or around it, lives in a cell of its own, which the code around it
and every function that uses it hold, so they all see the one variable.
Each time its declaration runs it makes a new cell, so each call of
counter gives a counter of its own. A function that calls itself can be
the first value of its own variable:
func(int) int fact = func(int k) int do
if k <= 1 then
return 1
end
return k * fact(k - 1)
endThe other variables a function uses, which nothing changes, are copied into
it, which is the same. Parameters and loop variables are copies too, and a
function made there cannot assign to them: copy one into a variable to
change it from there. shared before a variable in a function, as earlier
versions needed, still works, and cuarzo lint says it can go.
Generic functions
A function can take type parameters in brackets after its name, and then works for any types:
func first[T](T[] items, T fallback) T do
if len(items) == 0 then
return fallback
end
return items[0]
end
print(first([3, 1, 2], 0), first(["a"], "none")) // 3 aThe type arguments come from the arguments, from left to right, so a
function value passed after an array can take its parameter types from the
array. The compiler generates the function once for each set of types it is
used with, and checks it for those types: sort on an array of structs is
an error, since structs cannot be compared with <, and the error says
where the function was used. A generic function cannot itself be passed as
a value; wrap it, as in func(x) => first(x, 0).
A generic function can call a function of a type parameter, written as
for a type, as in T.from_json(value) or T.default(): each use calls the
function of its type argument, and a type argument without it is an error
at that use. So code that is the same for many types, as decoding the
arguments of a command with derived JSON code, is written once:
func call_json[A, R](func(A) R f, Json input) Result[Json] do
A value = A.from_json(input)?
return Result.OK(f(value).to_json())
endWorking with arrays through functions
Every program has these generic functions:
| Function | Result |
|---|---|
map(items, f) |
a new array with f of each element |
filter(items, keep) |
a new array with the elements for which keep is true |
reduce(items, start, combine) |
start combined with each element in turn |
any(items, test), all(items, test) |
whether test holds for some or for every element |
count(items, test) |
how many elements test holds for |
sort(items) |
sorts numbers, characters, or strings in place, smallest first |
sort_by(items, less) |
sorts in place so each element comes before those it is less than |
int[] numbers = [5, 3, 8, 1]
print(join(map(numbers, func(n) => to_str(n * 2)), " ")) // 10 6 16 2
print(reduce(numbers, 0, func(sum, n) => sum + n)) // 17
sort_by(people, func(a, b) => a.age < b.age)sort and sort_by are stable merge sorts: equal elements keep their order.
Their names, like those of the other functions here, cannot be declared
again.