Functions, step by step¶
A function gives a small piece of work a name. You can call it again with different values, while keeping the steps in one place.
Each example below is a complete program. Save one in functions.kiroha
and run kiroha run functions.kiroha --json when you'd like to try it.
Start with a name¶
fun greet(name:str)
"Hello, " + name + "!"
end fun greet
greet("Ada")
// "Hello, Ada!"
The parts are:
| Part | What it means |
|---|---|
fun greet(name:str) |
Define greet, with one string input called name. |
"Hello, " + name + "!" |
Calculate the result. |
end fun greet |
Finish the definition, repeating its name. |
greet("Ada") |
Call the function with a value. |
The name after end fun must match exactly, including capital letters.
Repeating it makes the boundary clear when you're reading or generating code.
No then or separator after the named end is needed. The then in an if
expression is still required.
A named function call uses parentheses. Supply one ordinary input for each parameter,
in the same order as the definition.
Keep a few local values¶
A body can contain several steps, separated by semicolons:
fun total(price:num, quantity:num)
subtotal = price * quantity;
tax = subtotal * 0.1;
subtotal + tax
end fun total
total(12.5, 2)
// 27.5
subtotal and tax belong to this call. The last expression is returned.
You don't write return.
A name is immutable: it keeps the value you gave it. Use a new name for a new step. Local names share a scope with the function's parameters, so choose a different name rather than redefining a parameter.
An empty body, or one ending in a binding or another definition, returns
null. A trailing semicolon after the last expression keeps that expression's
value.
Call the function from itself¶
A named function can call itself. Give a recursive function its result type
explicitly; :int below means an integer result. Here is a small factorial:
fun fac(n:int):int
if n <= 1 then 1 else n * fac(n - 1)
end fun fac
fac(5)
// 120
Read the if as an expression: choose 1 when n <= 1, or calculate the
other branch. Only the chosen branch runs.
For long loops, a call in tail position uses bounded continuation space. The language guide includes a counting example when you're ready to explore that.
Pass a small piece of work¶
Functions such as map take a trailing callback:
[1, 2, 3].map{n => n * 2}
// [2,4,6]
The braces describe what to do for each item. n is the current item, and
n * 2 is its result. Here .map{...} needs no ordinary arguments, so the callback follows the
method name directly. With ordinary arguments, write xs.fold(0){...}.
You can declare a callback slot on your own function too:
fun twice(value:num){step:(num)=>num}:num
first = step(value);
step(first)
end fun twice
twice(2){n => n * 3}
// 18
value:num declares a numeric input. {step:(num)=>num} declares a
separate, required callback that accepts and returns a number. At the call, {n => n * 3} fills that slot. The function applies it twice.
A complete {name} immediately after the parameter list declares the slot.
To start a body with a shorthand record, write {name:name} or do{{name}}.
A callback with two inputs uses {(a, b) => ...}; a callback with no inputs
uses {() => ...}.
Keep definitions close to their use¶
A function can see earlier bindings and its own name. It can't refer to a name defined later in the same scope. Put a helper before the function that uses it, or define it inside that function.
fun welcome(name:str)
fun decorate(text:str)
"[" + text + "]"
end fun decorate
decorate("Welcome, " + name)
end fun welcome
welcome("Lin")
// "[Welcome, Lin]"
The inner definition has its own matching end. If a name is missing or mismatched, Kiroha points to the syntax error before running the program.
Parameter types are explicit. Local values such as subtotal and callback
parameters such as n get their types from their nearby context. See
Types and simple generics when you need reusable data shapes
or generic functions. Missing information never silently becomes any.
Next, try these ideas on a list in Work with data. If you're updating an older program, the 0.6 migration guide shows the new annotations and validation boundaries.