# Chapter 4: Functions

Functions in native Loup look like functions in any Loup mode, with every parameter and return type written out. This chapter covers declarations, default and named arguments, function values and closures, generic functions, generators, properties, and annotations, and ends with a first look at the parameter verbs that chapter 15 covers in full.

## Declaring functions

A function is declared with `func`, its parameters with `name: Type`, and its return type after a colon:

```loup
@mode(host.native)

func connectionString(host: string, port: int = 5432, tls: bool = true): string {
    let scheme = if (tls) { "postgres+tls" } else { "postgres" };
    return i"{$ scheme $}://{$ host $}:{$ port $}";
}

func main(): void {
    println(connectionString("db.example.com"));
    println(connectionString("db.example.com", 5433));
    println(connectionString("db.example.com", port = 5433, tls = false));
    println(connectionString(host: "db", tls: false));
}
```

*Listing 4-1: Default and named arguments*

```text
postgres+tls://db.example.com:5432
postgres+tls://db.example.com:5433
postgres://db.example.com:5433
postgres://db:5432
```

`void` means the function returns no value. Parameters with defaults come after those without, and a default is evaluated again on every call (ruling DEF-1), so a default of `[]` gives each call a fresh empty list. Any argument can be passed by name, written `name = value` or `name: value`; the two spellings mean the same thing. Named arguments make calls with several parameters of the same type readable. The words `reads`, `edits`, `takes` and `shares` followed by a colon are ownership acknowledgements (see the end of this chapter), so they cannot be used as parameter names.

### Mistake: syntax from another language

Rust's `fn` and `->` are not Loup syntax. `fn` appears only in function *types*, and there is no arrow before a return type:

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

fn area(width: int, height: int) -> int {
    return width * height;
}

func main(): void {
    println(area(3, 4));
}
```

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

The parser does not recognize the declaration at all, so the messages are about what it expected instead. Write `func area(width: int, height: int): int`.

### Mistake: a path that does not return

A function with a return type must return a value on every path:

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

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

func main(): void {
    println(sign(0));
}
```

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

(Stage0 reports this one at the start of the file rather than at the function.) Add the missing case, here a final `return "zero";`. Calls are checked too: `area(3)` is "function 'area' expects 2 argument(s), but 1 provided", and `area(3, "4")` is "argument 2 of 'area': expected 'int', found 'string'".

> ⚠ **Planned.** Rest parameters (`*messages: list[string]`), a bare `*` marking keyword-only parameters, and `**options` keyword rest parameters are specified (Language Reference §1-7.4) but do not parse in 1.25.1. Pass a `list[T]` explicitly instead.

## Function values

Functions are values. A function type is written `fn(ParamTypes): ReturnType`, and an anonymous function (a lambda) is written with `func`, either with a block body or, for a single expression, with `=>`:

```loup
@mode(host.native)

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

func main(): void {
    let double = func(x: int): int { return x * 2; };
    let square = func(x: int) => x * x;
    let add: fn(int, int): int = func(a: int, b: int) => a + b;

    println(double(21));
    println(add(3, 4));
    println(applyTwice(square, 3));
    println([1, 2, 3, 4].map(square));
    println([1, 2, 3, 4, 5].filter(func(x: int) => x % 2 == 1));
    println([1, 2, 3, 4].fold(0, func(total: int, x: int) => total + x));
}
```

*Listing 4-2: Lambdas and function types*

```text
42
7
81
[1, 4, 9, 16]
[1, 3, 5]
10
```

### Mistake: leaving out a lambda's parameter types

If you are used to TypeScript or Kotlin you may expect the compiler to infer a lambda's parameter types from where it is passed. Stage0 does not:

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

func main(): void {
    println([1, 2, 3, 4].map(func(x) => x * x));
}
```

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

An unannotated parameter is `Any`, which matches neither `map`'s expected `fn(int): …` nor the `*` operator. Annotate it: `func(x: int) => x * x`.

## Closures

A lambda can use variables from the scope it was created in. It keeps them alive as long as it lives, so a function can return a closure over its own parameter:

```loup
#~ @mode(host.native)
func makeAdder(delta: int): fn(int): int {
    return func(x: int) => x + delta;
}

func main(): void {
    let addFive = makeAdder(5);
    println(addFive(10));
}
```

*Listing 4-3: Returning a closure*

```text
15
```

Reading a captured variable needs nothing special. *Assigning* to one does: a closure that changes a `var` of an enclosing function must say so with `nonlocal` (or `global` for a module variable) at the top of its body:

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

func main(): void {
    var requests = 0;
    let record = func(): void {
        requests = requests + 1;
    };
    record();
    println(requests);
}
```

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

Without the declaration, an assignment inside a closure would be ambiguous: Python, for example, silently creates a new local. Loup makes you choose:

```loup
#~ @mode(host.native)
#~ func main(): void {
var requests = 0;
let record = func(): void {
    nonlocal requests;
    requests = requests + 1;
};
record();
record();
println(requests);
#~ }
```

*Listing 4-4: A closure that updates an outer variable*

```text
2
```

A closure that captures a class instance holds a strong, reference-counted reference to it. Chapter 9 explains why that matters, and chapter 17 shows that capturing a `unique` value moves it into the closure.

> ⚠ **Planned.** Capture lists, `func() [weak self] { … }` and `[unowned self]`, are specified for `host.native` and `host.dual` as the way to stop a closure stored on an object from keeping that object alive (Language Reference §1-7.3). Stage0 1.25.1 rejects every capture list: `capture lists are only allowed in @compiled files targeting --target native`, even in a native build. Until they land, break such cycles by clearing the stored closure when you are done with it.

## Generic functions

Type parameters go in square brackets after the name. The compiler infers them from the arguments, or you can pass them explicitly:

```loup
#~ @mode(host.native)
func firstOr[T](items: list[T], fallback: T): T {
    if (items.length() == 0) {
        return fallback;
    }
    return items[0];
}

func main(): void {
    println(firstOr([3, 1, 2], 0));
    println(firstOr[string]([], "none"));
}
```

*Listing 4-5: A generic function*

```text
3
none
```

Chapter 11 covers generics in depth, including bounds such as `T: Comparable[T]`.

## Generators

A `gen func` produces a sequence lazily: each `yield` hands one value to the caller's loop and suspends the function until the next value is wanted. Its return type is `Iterable[T]`:

```loup
#~ @mode(host.native)
gen func backoffDelays(attempts: int): Iterable[int] {
    var delay = 100;
    var i = 0;
    while (i < attempts) {
        yield delay;
        delay = delay * 2;
        i = i + 1;
    }
}

func main(): void {
    for (ms in backoffDelays(4)) {
        println(i"retry after {$ ms $} ms");
    }
}
```

*Listing 4-6: A generator of retry delays*

```text
retry after 100 ms
retry after 200 ms
retry after 400 ms
retry after 800 ms
```

> ⚠ **Planned.** In Loup 1.25.1 the native code generator does not lower generators yet, and neither do the list methods that take a function value (`map`, `filter`, `fold`): `loup build --target native` stops with an `E-BUILD-NATIVE` error naming the construct. They run under `loup run`, and a `host.dual` program can use them in a WebAssembly build. Ordinary closures such as Listing 4-3 build natively.

## Properties

A class can expose computed properties with `get` and `set` accessors. From the outside they look like fields:

```loup
#~ @mode(host.native)
class Temperature {
    var celsiusValue: float;

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

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

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

func main(): void {
    let t = Temperature(20.0);
    println(t.fahrenheit);
    t.fahrenheit = 212.0;
    println(t.fahrenheit);
}
```

*Listing 4-7: A computed property*

```text
68
212
```

Keep accessors cheap and free of surprises; a reader seeing `t.fahrenheit` expects a field read. Chapter 9 covers classes.

## Annotations are not decorators

Names that start with `@` are compiler annotations with fixed meanings: `@mode`, `@overflowWrapping` (chapter 3), `@unsafe("reason")` (chapter 19), and so on. Loup 1.25.1 has no user-defined decorators. An unknown annotation is only a warning, so a Python-style decorator compiles and silently does nothing:

```loup,not_desired_behavior
@mode(host.native)

func logged(f: fn(int): int): fn(int): int {
    return func(x: int): int {
        println(i"calling with {$ x $}");
        return f(x);
    };
}

@logged
func triple(x: int): int {
    return x * 3;
}

func main(): void {
    println(triple(4));
}
```

```text
12
main.lphc:10:1: warning: Unknown annotation '@logged'
```

`triple` was never wrapped: there is no "calling with 4" line. Read warnings; this one says the annotation was ignored. Wrap explicitly instead: `let loggedTriple = logged(triple);`.

## A first look at parameter verbs

Every parameter has an ownership *verb* saying what the function does with the argument. With no verb, a parameter is `reads`: the function may look at the argument but not change it. That is why this compiles in Python but not in Loup:

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

func appendSentinel(scores: list[int]): void {
    scores.push(0);
}

func main(): void {
    var scores = [90, 72];
    appendSentinel(scores);
    println(scores);
}
```

```text
main.lphc:4:5: error[E_READS_MUTATION]: E_READS_MUTATION: parameter 'scores' is a `reads` borrow (a parameter without a verb is read-only, §3-2.4.9) and cannot be mutated (`push` called on it); declare it `edits` and pass `edits: …` to change the caller's value, or copy it into a local first
```

The message gives both fixes. To change the caller's list, declare the parameter `edits` and acknowledge it at the call site:

```loup
#~ @mode(host.native)
func appendSentinel(edits scores: list[int]): void {
    scores.push(0);
}

func main(): void {
    var scores = [90, 72];
    appendSentinel(edits: scores);
    println(scores);
}
```

*Listing 4-8: An `edits` parameter*

```text
[90, 72, 0]
```

The `edits:` at the call site is required: anyone reading `main` can see that the call changes `scores`. Chapter 15 covers all four verbs (`reads`, `edits`, `takes`, `shares`) and the rules behind them.

## Summary

- Functions are declared `func name(p: T, …): R`; defaults are re-evaluated per call, and arguments can be passed by name with `=` or `:`.
- Rest and keyword-only parameters are ⚠ planned.
- Function types are `fn(A): R`; lambdas are `func(x: T) => expr` or `func(x: T): R { … }`, and their parameter types must be written.
- Closures capture their environment; assigning to a captured `var` needs `nonlocal` (`E_GLOBAL_MISSING`). Capture lists (`[weak self]`) are ⚠ planned.
- `gen func` returns an `Iterable[T]`. Generators and the function-taking list methods run under `loup run` but are not yet lowered by native builds.
- `get`/`set` accessors define properties; unknown `@annotations` are ignored with a warning, so there are no user decorators.
- A parameter without a verb is `reads`; mutating it is `E_READS_MUTATION`. Use `edits` and `edits:` to change the caller's value.
- Chapter 5 covers control flow.
