All documentation pages
Functions
Functions are where a Pudu program does its work. This chapter covers declaring them, calling them, giving parameters defaults, passing functions as values, and writing function literals.
Declaring a function
A function names its parameters with their types and states what it returns:
module Area
fn rectangleArea(width: Int, height: Int) -> Int {
width * height
}
fn main() -> Int {
if rectangleArea(3, 4) == 12 { 0 } else { 1 }
}The last expression in the body is the result, so there is no return in rectangleArea. A function that produces nothing useful returns (), the unit value, and may leave the return type out when it is not exported.
Expression bodies
A function whose whole body is one expression can say so with =:
module Doubling
fn double(n: Int) -> Int = n * 2
fn square(n: Int) -> Int = n * n
fn main() -> Int {
if double(square(3)) == 18 { 0 } else { 1 }
}Returning early
return leaves a function straight away with a value. It reads best at the top of a function, dealing with the special cases before the main work:
module Grading
fn grade(score: Int) -> Str {
if score < 0 || score > 100 { return "invalid" }
if score >= 90 { return "excellent" }
"scored {score}"
}
fn main() -> Int {
let ok = grade(-5) == "invalid" && grade(95) == "excellent" && grade(70) == "scored 70"
if ok { 0 } else { 1 }
}Default values
A parameter can have a default, used when a call leaves the argument out. Parameters with defaults come after the ones without:
module Defaults
fn greet(name: Str, greeting: Str = "Hello", punctuation: Str = "!") -> Str {
"{greeting}, {name}{punctuation}"
}
fn main() -> Int {
let plain = greet("Ada")
let warm = greet("Ada", "Welcome")
let quiet = greet("Ada", "Hi", ".")
if plain == "Hello, Ada!" && warm == "Welcome, Ada!" && quiet == "Hi, Ada." { 0 } else { 1 }
}Recursion
A function may call itself. There is no special syntax, and the recursion ends when a branch stops calling:
module Factorial
fn factorial(n: Int) -> Int {
if n <= 1 { 1 } else { n * factorial(n - 1) }
}
fn fibonacci(n: Int) -> Int {
if n < 2 { n } else { fibonacci(n - 1) + fibonacci(n - 2) }
}
fn main() -> Int {
if factorial(5) == 120 && fibonacci(10) == 55 { 0 } else { 1 }
}For long-running work, a loop is usually clearer and uses less memory than deep recursion; the control flow chapter covers loops.
Functions are values
A function can be stored in a binding, passed to another function, and kept in a collection. Its type is written fn(ParameterTypes) -> Result:
module Values
fn double(n: Int) -> Int = n * 2
fn increment(n: Int) -> Int = n + 1
fn applyTwice(change: fn(Int) -> Int, start: Int) -> Int {
change(change(start))
}
fn main() -> Int {
let steps = [double, increment]
var value = 5
for step in steps {
value = step(value)
}
if applyTwice(double, 3) == 12 && value == 11 { 0 } else { 1 }
}Function literals
A function literal is a function written where it is used. The short form, with =>, has a single expression as its body; the long form has a block and states its return type:
module Literals
fn main() -> Int {
let numbers = [1, 2, 3, 4, 5, 6]
let evens = numbers.filter(fn(n: Int) => n % 2 == 0)
let labels = evens.map(fn(n: Int) -> Str {
let doubled = n * 2
"{n} doubles to {doubled}"
})
if evens == [2, 4, 6] && labels[0] == "2 doubles to 4" { 0 } else { 1 }
}A literal can use the bindings around it. It captures them by copying, so it sees their values as they were when it was written, and it cannot change them:
module Capturing
fn adder(amount: Int) -> fn(Int) -> Int {
fn(n: Int) => n + amount
}
fn main() -> Int {
let addTen = adder(10)
let addOne = adder(1)
if addTen(5) == 15 && addOne(5) == 6 { 0 } else { 1 }
}adder returns a function. Each call makes a new one that remembers its own amount.
Generic functions
A function can work for many types by naming a type parameter in square brackets. The compiler works out the type at each call:
module Generic
import Std.Option as Option
fn firstOr[T](items: &Array[T], fallback: T) -> T {
if items.isEmpty() { fallback } else { items[0] }
}
fn pairUp[A, B](left: A, right: B) -> (A, B) {
(left, right)
}
fn main() -> Int {
let number = firstOr(&[7, 8, 9], 0)
let word = firstOr(&[], "none")
let pair = pairUp("age", 36)
if number == 7 && word == "none" && pair[1] == 36 { 0 } else { 1 }
}A type parameter can also require behaviour, such as T: Ord for values that can be compared. That is covered with traits.
Parameters that change what they are given
A parameter normally receives a copy of a value or a read-only borrow of it. A parameter typed &mut T may change the caller's value, and the caller writes &mut to agree:
module Changing
fn addBonus(score: &mut Int, bonus: Int) -> () {
*score = *score + bonus
}
fn main() -> Int {
var score = 40
addBonus(&mut score, 2)
if score == 42 { 0 } else { 1 }
}The ownership chapter explains borrowing in full.
Exported functions
A function marked export can be imported by other modules. Its parameter types and return type must be written out, because other modules are checked against its signature alone.
