Functions
Naming a piece of work, giving it inputs and an answer, letting callers leave out arguments, and passing arguments by name so the call explains itself.
A function is a name for a piece of work
A function takes inputs, does one thing, and hands back an answer. fun greet(name: String) = "Hello, $name" reads as: the word fun, the name of the function, then parentheses holding each input with its type, then the answer. The types are part of the signature, so the compiler knows what a caller may pass before it looks at the body.
Write one when you can say what it does in a few words. A name like taxOn or areaOf lets the reader skip the body and trust the call, which is the only reason to move code out of main in the first place. A function that needs "and" to describe it is usually two functions, and splitting it makes both names easier to trust.
Two ways to write the body
When the body is a single expression, an equals sign replaces the braces and the compiler works out the return type, so a small multiplier fits on one line. When the body needs several lines, use braces and write return on the line that produces the answer, with the return type after the closing parenthesis: fun describe(...): String { ... }. The expression form is not a shortcut for the block form; it is the same function with one fewer layer.
Everything outside main in the file is a top-level function, and any function can call any other. Order in the file does not matter, so put the helper a reader needs first next to the function that uses it.
Defaults, and arguments by name
A parameter may carry a default value: fun area(width: Int, height: Int = 4). A caller who agrees with the default leaves that argument out, and the call still works. That is how a function stays short to call in the common case without losing the option in the rare one. The default is written once, in the signature, rather than repeated at every call site.
A caller may also name the arguments, in any order: area(height = 2, width = 5). The call then reads like a sentence, and it survives a later edit that swaps two parameters — the kind of change that silently breaks every positional call. Naming one argument does not force you to name the rest, but a call that mixes both styles should name the ones that would otherwise be ambiguous.
What a call actually does
A call creates a new frame for the function: each parameter gets the value of its argument, the body runs with those names, and the answer is handed back to the caller. When the function returns, its local names disappear. A second call starts from scratch, so a value left in a local name by the first call is not there for the second.
That is why a function cannot remember a count between calls unless the count lives outside it, and why passing the same arguments twice gives the same answer for a function that only reads them. A function that prints instead of returning still does its work, but the caller receives Unit rather than the text; the text went to the output, not to the call.
Four functions, three kinds of call
fun greet(name: String) = "Hello, $name"
fun area(width: Int, height: Int = 4) = width * height
fun tax(cents: Int, percent: Int = 20) = cents * percent / 100
fun describe(name: String, role: String = "teacher"): String {
return name + " works as a " + role
}
fun main() {
println(greet("Ada"))
println(area(3)) // 12
println(area(3, 10)) // 30
println(area(height = 2, width = 5)) // 10
println(tax(2000)) // 400
println(tax(percent = 7, cents = 2000)) // 140
}Calling a function four ways
Run it and match each printed line to its call. Then add a function of your own with two parameters and one default, and call it once with everything given and once with only the first argument.
A call is a small, temporary world
When you call a function, each parameter is bound to the argument for that call, the body runs with those names, and the answer comes back to the caller. The names inside the function do not exist outside it, and a second call gets a fresh set of them. That is what lets two functions use total without interfering, and why a local value cannot be read from main after the call returns.
An expression body returns its expression; a block body returns whatever a return hands back. In both forms the return type is part of the promise the caller relies on. Default arguments are filled in at the call site, which is why a caller can leave one out, and named arguments are matched to parameters by name rather than by position.
Three function mistakes are easy to make. A block body that reaches its closing brace without a return hands back Unit, so declare the return type and let the compiler point at the missing path. Positional arguments are matched by position, not by name, so two Int parameters can be swapped without a compile error. And a default argument can be skipped only by naming the arguments that follow it, so f(2) fills the first parameter rather than the second.
Try these next
- Write
fun double(n: Int) = n * 2and call it from inside aprintln. - Give a function two defaults, then call it three ways: all positional, one named, all named.
- Write a block-body function with an early
returnfor one case and a finalreturnfor the rest.
One price function, three calls
Run it and match each printed number to its call. Then change the default tax rate, add a third parameter with its own default, and call the function once with every argument named.
Notice how much of the file is the function signature rather than the body. In Kotlin the names and types are the documentation a reader trusts, so a function whose signature is clear rarely needs a comment above it.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does a default argument let a caller do?
