# Chapter 3: Functions and Closures

**What you'll learn:** how to declare functions, give parameters default values and pass arguments by name; how to write lambdas and closures, including closures that update outer variables; and how generators produce values lazily.

Functions are how you name a piece of logic and reuse it. Every call is checked: the number of arguments, their types and what you do with the result.

## Declaring Functions

```loup
@mode(host.scripted)

func add(a: int, b: int): int {
    return a + b;
}

func greet(name: string): void {
    println(i"Hello, {$ name $}!");
}

println(add(3, 4));
greet("Alice");
```

*Listing 3-1: A function that returns a value and one that does not*

```text
7
Hello, Alice!
```

- `func` starts the declaration, followed by the name and the parameter list.
- Each parameter is `name: Type`.
- The return type follows the parameter list after a colon. A function called only for its effect returns `void`.
- A function can be called before the line that declares it.

The compiler checks every call against the declaration. Too few arguments is `function 'add' expects 2 argument(s), but 1 provided`; a wrong type is `argument 2 of 'add': expected 'int', found 'string'`.

### Mistake: syntax from another language

If you write Rust, Swift or Kotlin, you might reach for `fn` and `->`:

```loup,does_not_compile
@mode(host.scripted)

fn add(a: int, b: int) -> int {
    return a + b;
}
```

```text
main.lpscr:3:1: error[E0001]: expected expression
main.lpscr:3:4: error[E0003]: expected `;`
main.lpscr:5:1: error[E0001]: expected expression
```

The messages do not say "use `func`", so learn the shape: Loup declares functions with `func` and puts the return type after a colon, `func add(a: int, b: int): int`. The word `fn` exists only in function *types* (see "Functions as values" below).

### Mistake: leaving out the return type

A function declared without a return type returns `void`, even if its body returns a value:

```loup,does_not_compile
@mode(host.scripted)

func double(n: int) {
    return n * 2;
}

let d: int = double(4);
println(d);
```

```text
main.lpscr:7:1: error[E0001]: cannot assign 'void' to binding 'd' of type 'int'
```

Write the return type: `func double(n: int): int`. This book always writes it.

### Mistake: a path that does not return

```loup,does_not_compile
@mode(host.scripted)

func sign(n: int): string {
    if (n > 0) {
        return "positive";
    } else if (n < 0) {
        return "negative";
    }
}

println(sign(5));
```

```text
main.lpscr:1:1: error[E0001]: function 'sign' may not return a value on all code paths
```

When `n` is `0`, neither branch runs and the function would fall off its end. (The position in the message is not useful today; the function name is.) Add the missing case:

```loup
@mode(host.scripted)

func sign(n: int): string {
    if (n > 0) {
        return "positive";
    } else if (n < 0) {
        return "negative";
    }
    return "zero";
}

println(sign(5));
println(sign(0));
```

```text
positive
zero
```

## Parameters

Parameters are immutable, like `let` bindings. Assigning to one is `cannot assign to immutable binding`; make a local copy instead:

```loup
#~ @mode(host.scripted)
func normalize(input: string): string {
    let cleaned = input.trim();
    return cleaned.toLowerCase();
}

println(normalize("  Hello World  "));
```

```text
hello world
```

### Default values and named arguments

A parameter can have a default value. Any argument can be passed by name, written `name = value`:

```loup
@mode(host.scripted)

func connect(host: string, port: int = 8080, tls: bool = true): void {
    println(i"connecting to {$ host $}:{$ port $} (tls: {$ tls $})");
}

connect("example.com");
connect("example.com", 3000);
connect("example.com", tls = false);
connect(port = 22, host = "build-server");
```

*Listing 3-2: Defaults and named arguments*

```text
connecting to example.com:8080 (tls: true)
connecting to example.com:3000 (tls: true)
connecting to example.com:8080 (tls: false)
connecting to build-server:22 (tls: true)
```

Named arguments may skip any parameter that has a default and can come in any order. Once a parameter has a default, every parameter after it must have one too (`E_PARAM_ORDER`). A default expression is evaluated again on every call that omits the argument, so `func f(items: list[int] = [])` gets a fresh empty list each time (§1-7.1.1).

The Language Reference also allows `name: value` for a named argument; the two forms mean the same thing. This book uses `=` at call sites.

> ⚠ **Planned.** Rest parameters (`*messages: list[string]`), keyword-only parameters after a bare `*`, and `**options: dict[string, Any]` are specified in §1-7.4. The 1.25 parser does not accept them. Until it does, pass a `list` or a `dict` explicitly.

### Untyped parameters

In a script you may leave a parameter's type out; it is then `Any`:

```loup
#~ @mode(host.scripted)
func show(label, value): void {
    println(i"{$ label $} = {$ value $}");
}

show("count", 42);
show("name", "Alice");
```

```text
count = 42
name = Alice
```

This is convenient in a quick script, but the compiler can no longer check what you do with `value`. Write the types in anything you keep.

### Mistake: a reserved word as a parameter name

Several common English words are Loup keywords: `from`, `type`, `match`, `in`, `attempt`, `spawn` and others (the list is in §1-3.4). Using one as a name fails:

```loup,does_not_compile
@mode(host.scripted)

func countdown(from: int): void {
    var n = from;
    while (n > 0) {
        println(n);
        n = n - 1;
    }
}
```

```text
main.lpscr:3:16: error[E0001]: `from` is a reserved word (§1-3.4) and cannot name a parameter
  note: rename the parameter — e.g. `from_value`
```

Rename it — `start` reads better than the suggested `from_value`:

```loup
@mode(host.scripted)

func countdown(start: int): void {
    var n = start;
    while (n > 0) {
        println(n);
        n = n - 1;
    }
}

countdown(3);
```

```text
3
2
1
```

Not every keyword gets such a helpful message: `var attempt = 0;` reports only `expected identifier` and `expected ';'`. If a perfectly ordinary-looking declaration fails to parse, check the name against the keyword list.

## Functions as Values

Functions are values. A function's type is written with `fn`: `fn(int): int` takes an `int` and returns an `int`. A lambda is a function without a name, written with `func`:

```loup
@mode(host.scripted)

func applyTwice(f: fn(int): int, value: int): int {
    return f(f(value));
}

func triple(n: int): int {
    return n * 3;
}

let square: fn(int): int = func(n: int): int {
    return n * n;
};
let increment = func(n: int) => n + 1;

println(applyTwice(triple, 2));
println(applyTwice(square, 3));
println(applyTwice(increment, 10));
```

*Listing 3-3: Named functions and lambdas passed as values*

```text
18
81
12
```

A lambda has two forms. The block form `func(n: int): int { … }` needs a `return`; the expression form `func(n: int) => n + 1` returns the expression's value.

Lambdas are what you pass to the collection methods (Chapter 5):

```loup
#~ @mode(host.scripted)
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let sumOfEvenSquares = numbers
    .filter(func(n: int) => n % 2 == 0)
    .map(func(n: int) => n * n)
    .reduce(0, func(total: int, n: int) => total + n);

println(sumOfEvenSquares);
```

```text
220
```

### Known gap: lambda parameter types are not inferred

The Language Reference (§1-7.2) lets you leave a lambda's parameter types out when the context determines them, as in `numbers.map(func(x) => x * 2)`. The 1.25 compiler does not infer them; the parameter becomes `Any` and the call is rejected:

```loup,does_not_compile
@mode(host.scripted)

let numbers = [1, 2, 3];
let doubled = numbers.map(func(x) => x * 2);
println(doubled);
```

```text
main.lpscr:4:27: error[E0001]: argument 1 of 'map': expected 'fn(int): <error>', found 'fn(Any): <error>'
main.lpscr:4:38: error[E_OPERATOR_MISSING]: type 'Any' does not implement 'Mul' required for operator '*' (E_OPERATOR_MISSING)
```

Annotate the parameter, `func(x: int) => x * 2`, and it works.

## Closures

A lambda can use variables from the scope where it was created. It *captures* them, and they stay alive as long as the lambda does:

```loup
@mode(host.scripted)

func makeAdder(n: int): fn(int): int {
    return func(x: int) => x + n;
}

let add5 = makeAdder(5);
let add10 = makeAdder(10);

println(add5(3));
println(add10(3));
```

```text
8
13
```

Each call to `makeAdder` produces a separate closure with its own `n`.

Capture is by reference: a closure that reads an outer `var` sees its current value, not the value it had when the closure was made.

```loup
#~ @mode(host.scripted)
var message = "hello";
let printMessage = func(): void { println(message); };

printMessage();
message = "world";
printMessage();
```

```text
hello
world
```

### Mistake: assigning an outer variable from a closure

Reading an outer variable needs nothing special. *Assigning* to one does:

```loup,does_not_compile
@mode(host.scripted)

func makeCounter(step: int): fn(): int {
    var count = 0;
    return func(): int {
        count = count + step;
        return count;
    };
}

let next = makeCounter(2);
println(next());
```

```text
main.lpscr:6:9: error[E_GLOBAL_MISSING]: E_GLOBAL_MISSING: assignment to outer-scope `var` `count` inside a closure needs a `nonlocal count` (an enclosing function's variable) or `global count` (a module variable) declaration at the top of the closure's body (§1-7.3)
```

Loup wants you to say that the closure changes something outside itself. `count` belongs to the enclosing function, so declare it `nonlocal` as the first statement of the closure:

```loup
@mode(host.scripted)

func makeCounter(step: int): fn(): int {
    var count = 0;
    return func(): int {
        nonlocal count;
        count = count + step;
        return count;
    };
}

let next = makeCounter(2);
println(next());
println(next());
println(next());
```

*Listing 3-4: A counter closure that updates its captured variable*

```text
2
4
6
```

For a variable at the top level of the script, write `global name;` instead. Only closures need the declaration: a top-level `func` may assign a top-level `var` directly.

## Generic Functions

A function can take type parameters in square brackets. This retry helper works for work that returns any type `T`:

```loup
@mode(host.scripted)

func retry[T](times: int, work: fn(): T): T {
    var failures = 0;
    while (true) {
        try {
            return work();
        } catch (Exception as e) {
            failures = failures + 1;
            if (failures >= times) {
                throw e;
            }
            println(i"attempt {$ failures $} failed: {$ e.message $}");
        }
    }
}

var calls = 0;
let flakyFetch = func(): string {
    global calls;
    calls = calls + 1;
    if (calls < 3) {
        throw RuntimeError(i"timeout #{$ calls $}");
    }
    return "payload";
};

println(retry(5, flakyFetch));
```

*Listing 3-5: A generic retry helper built from a closure*

```text
attempt 1 failed: timeout #1
attempt 2 failed: timeout #2
payload
```

`retry` knows nothing about fetching; the caller's closure supplies the work. If every attempt fails, the last exception is thrown again to the caller. Chapter 9 covers generics and Chapter 10 covers `try`/`catch`.

## Generators

A generator produces a sequence one value at a time. Declare it with `gen func`, give it the return type `Iterable[T]`, and `yield` each value. The body runs only as far as the consumer asks:

```loup
@mode(host.scripted)

gen func naturals(): Iterable[int] {
    var n = 1;
    while (true) {
        yield n;
        n = n + 1;
    }
}

gen func take[T](source: Iterable[T], count: int): Iterable[T] {
    var taken = 0;
    for (value in source) {
        if (taken >= count) {
            return;
        }
        yield value;
        taken = taken + 1;
    }
}

for (n in take(naturals(), 5)) {
    println(n * n);
}
```

*Listing 3-6: An infinite generator, limited by another generator*

```text
1
4
9
16
25
```

`naturals()` never ends on its own, but that is fine: each `yield` hands one value to the consumer and pauses until the next one is requested. `take` stops asking after five values, and a `return` inside a generator ends the sequence.

`async gen func` and `for await` (asynchronous generators) are covered with the rest of async code in Chapter 13.

## Computed Properties

Classes (Chapter 8) can declare *properties*: methods that are read and written with field syntax. A `get` runs when the property is read; a `set` runs when it is assigned, and like any method that changes an object it is called inside `safe { … }`:

```loup
@mode(host.scripted)

class Temperature {
    pub var celsius: float;

    new(celsius: float) {
        this.celsius = celsius;
    }

    pub get fahrenheit(): float {
        return this.celsius * 9.0 / 5.0 + 32.0;
    }

    pub set fahrenheit(value: float) {
        this.celsius = (value - 32.0) * 5.0 / 9.0;
    }
}

let t = Temperature(celsius = 100.0);
println(t.fahrenheit);
safe {
    t.fahrenheit = 41.0;
}
println(t.celsius);
```

```text
212
5
```

## Decorators

> ⚠ **Planned.** The Language Reference (§1-7.6) defines user decorators: `@logged func square(…)` would apply the function `logged` to `square` when it is declared. The 1.25 toolchain does not apply them; it prints `warning: Unknown annotation '@logged'` and leaves the function unchanged. Until decorators are implemented, wrap the function explicitly: `let loggedSquare = logged(square);`.

## Summary

- Declare functions as `func name(param: Type): ReturnType { … }`; there is no `fn` keyword and no `->`.
- Write the return type: without one, a function returns `void`. Every path of a value-returning function must `return`.
- Parameters are immutable. Defaults and named arguments (`name = value`) make calls readable; rest and keyword-only parameters are planned.
- Some common words (`from`, `type`, `match`, `attempt`, …) are reserved and cannot be names.
- Lambdas are `func(x: T) => expr` or `func(x: T): R { … }`; their type is `fn(T): R`. Annotate lambda parameters.
- Closures capture by reference; a closure that assigns an outer variable declares it `nonlocal` (or `global`).
- `gen func` with `yield` produces lazy, possibly infinite sequences.
- Properties use `get`/`set` in classes. User decorators are planned.

## Try It Yourself

1. Write `makeAccumulator(): fn(int): int` that returns a closure adding each argument to a running total and returning the total so far.
2. Write `gen func fibonacci(): Iterable[int]` and print the first ten Fibonacci numbers with the `take` generator from Listing 3-6.
3. Write `memoize(f: fn(int): int): fn(int): int` that caches results in a `dict[int, int]` captured by the returned closure. Which variable does the closure assign, and does it need `nonlocal`?
