Skip to content
Noodle
InstallLearnPlayground
GitHub

Functions

An ordinary function declaration has a name, typed parameters, a result type, and a body:

func name(parameter : Type) -> ResultType do
body
end

Every value parameter and result has an explicit type. Local bindings inside the body may be inferred.

The final expression of the body is the normal result:

func add(left : Int, right : Int) -> Int do
left + right
end

If the body ends after a semicolon-terminated statement, it produces Unit:

func greet(name : String) -> Unit do
Console.println("Hello, " ++ name ++ "!");
end

A call supplies one positional argument per positional parameter:

func area(width : Int, height : Int) -> Int do
width * height
end
func main() -> Unit do
result = area(6, 7);
Debug.trace(result);
end

The callee is evaluated first, then arguments from left to right. Each argument is evaluated exactly once.

Question parameters provide named inputs. Their basic form is a trailing ?{...} group in the declaration and call:

func rectangle(?{width: Int, height: Int}) -> Int do
width * height
end
func main() -> Unit do
area = rectangle(?{height: 7, width: 6});
Debug.trace(area);
end

Answers are matched by name, so callers may write them in any order. Every required answer must be present exactly once and have the declared type. Positional arguments, when a function has both forms, come before the question answer group.

Defaults, presence questions, compiler-filled source locations, and contextual capability parameters build on this syntax. Learn those forms in Question parameters.

Use return value; to leave the current function before its tail expression:

func clamp_nonnegative(value : Int) -> Int do
if value < 0 then
return 0;
end;
value
end

A bare return; is valid only when the function result is Unit.

A top-level function may call itself, and functions in the same source file may refer to declarations written later:

func factorial(value : Int) -> Int do
if value <= 1 then
1
else
value * factorial(value - 1)
end
end
func main() -> Unit do
Debug.trace(factorial(5));
end

Use recursion when the problem is naturally recursive. For ordinary counting or accumulation, a loop usually makes the evolving state and exit conditions easier to inspect.

A named local function can organize a helper that belongs only to one outer function:

func sum_to(limit : Int) -> Int do
func loop(current : Int, total : Int) -> Int do
if current == limit then
total
else
loop(current + 1, total + current)
end
end;
loop(0, 0)
end

Local functions are recursively bound. Mutually recursive local functions are written as one comma-separated function group.

Function values and lambdas are covered later in Higher-order functions.

Next: control flow.