Functions · Lesson 4.1

Function

Source
Write functions that take typed parameters and give back a result, and call them from Main.
You'll need: Mutable, Float, For

A function gives a piece of work a name. You write the work once, and then any part of the program can run it by calling that name — with different values each time. Main has been a function since the very first lesson; this lesson writes others and calls them from Main.

Functions are how a program stays readable as it grows. CelsiusToFahrenheit(100.0) says what it does, while 100.0 * 9.0 / 5.0 + 32.0 makes you work it out.

The shape of a function

Every function has the same parts: func, a name, the parameters in parentheses, -> and the type of the result, then a body in braces that hands the result back with return:

func Add(left: int, right: int) -> int {
    return left + right;
}
PartIn AddMeaning
NameAddHow callers refer to it — PascalCase, like Main
Parametersleft: int, right: intThe values the caller must supply, each typed
Result type-> intThe type of the value the function gives back
Body{ return left + right; }The work, ending in return

Calling a function

A call is the name followed by one argument per parameter, in the same order. Each argument becomes the value of its parameter for that one run of the body, and the value after return replaces the call:

flowchart LR
    call["Add(2, 3)"] --> bind["left = 2<br/>right = 3"]
    bind --> body["return left + right;"]
    body --> result["the call is<br/>worth 5"]

The result type is whatever the work produces. Here two float64 values go in and one float64 comes out:

func Average(first: float64, second: float64) -> float64 {
    return (first + second) / 2.0;
}

Arguments must match the parameter types exactly — nothing is converted for you. Square(2.5) is refused because Square takes an int and 2.5 is a float64.

Parameters cannot change

Inside the body a parameter behaves like a let binding: you can read it, but not assign to it. A function that needs a running value declares a local var of its own:

func SumUpTo(last: int) -> int {
    var total = 0;
    for i in 1..=last {
        total += i;
    }
    return total;
}

Each call gets its own fresh total, so calling SumUpTo twice never mixes the two sums.

A call is a value

Anywhere a value of the result type fits, a call fits too. It can be bound to a name, or passed straight into another call:

let side = Add(4, 5);
PrintLine("Add(Square(3), Square(4))    {}", Add(Square(3), Square(4)));

The inner calls run first: Square(3) is 9, Square(4) is 16, and only then does Add run, with 9 and 16 as its arguments.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// A function gives a piece of work a name, so it can be written once and run
// from as many places as need it. `Main` has been one since the first lesson;
// this lesson writes others and calls them from `Main`.
//
// The shape is always the same: `func`, a name, the parameters in parentheses,
// `->` and the type of the result, then a body that hands the result back with
// `return`.
import Io::PrintLine;

// Every parameter is written `name: type`, separated by commas. A call must
// supply one argument for each, in the same order.
func Add(left: int, right: int) -> int {
    return left + right;
}

func Square(value: int) -> int {
    return value * value;
}

// The result type is whatever the work produces. Here two `float64` values go
// in and one `float64` comes out.
func Average(first: float64, second: float64) -> float64 {
    return (first + second) / 2.0;
}

func CelsiusToFahrenheit(celsius: float64) -> float64 {
    return celsius * 9.0 / 5.0 + 32.0;
}

// Parameters are immutable inside the body, exactly like `let` bindings, so a
// running value needs a local `var` of its own.
func SumUpTo(last: int) -> int {
    var total = 0;
    for i in 1..=last {
        total += i;
    }
    return total;
}

func Main() -> int {
    PrintLine("Add(2, 3)                    {}", Add(2, 3));
    PrintLine("Square(7)                    {}", Square(7));
    PrintLine("Average(4.0, 7.0)            {}", Average(4.0, 7.0));
    PrintLine("CelsiusToFahrenheit(100.0)   {}", CelsiusToFahrenheit(100.0));
    PrintLine("SumUpTo(10)                  {}", SumUpTo(10));

    // A call is an ordinary value. It can be bound to a name...
    let side = Add(4, 5);
    PrintLine("side                         {}", side);

    // ...or passed straight into another call. The inner call runs first.
    PrintLine("Square(Add(1, 2))            {}", Square(Add(1, 2)));
    PrintLine("Add(Square(3), Square(4))    {}", Add(Square(3), Square(4)));

    // Arguments must match the parameter types. `Square(2.5)` is rejected,
    // because `2.5` is a `float64` and `Square` takes an `int` — the value is
    // not converted for you. And assigning to a parameter inside its function
    // is an error:
    //
    //     func Bump(n: int) -> int { n = n + 1; return n; }
    //         error: cannot modify parameter 'n'
    return 0;
}

Run it

cd Examples/Functions/Function
rux run
Add(2, 3)                    5
Square(7)                    49
Average(4.0, 7.0)            5.5
CelsiusToFahrenheit(100.0)   212.0
SumUpTo(10)                  55
side                         9
Square(Add(1, 2))            9
Add(Square(3), Square(4))    25

Common mistakes

An argument of the wrong type.
Square(2.5) fails with error: argument 1 to 'Square' has type 'float64', but parameter 'value' requires 'int'. Pass a value of the parameter's type, or convert it with as as in Convert.
Too few or too many arguments.
Add(2) fails with error: call to 'Add' expects 2 arguments, but 1 was provided. Every parameter needs an argument — until Default argument shows how to make one optional.
Assigning to a parameter.
func Bump(n: int) -> int { n = n + 1; return n; } fails with error: cannot modify parameter 'n'. Copy the parameter into a local var and change that instead, as SumUpTo does with total.
Getting the name's case wrong.
Names are case-sensitive: square(2) fails with error: name 'square' is not defined in this scope, and the compiler suggests did you mean 'Square'?.

Try it yourself

  1. Write Cube(value: int) -> int that returns value * Square(value), and print Cube(3).
  2. Write FahrenheitToCelsius and check that it turns 212.0 back into 100.0.
  3. Write Product(last: int) -> int that multiplies the numbers from 1 to last, following the shape of SumUpTo.
  4. Call Square(2.5) and Add(2) and read both messages.

Learn more