# Chapter 4: Functions

**What you'll learn.** How to declare functions, default and named arguments, lambdas and closures, function types, higher-order functions, a first look at generic functions and generators, property accessors, and what `pub` means.

**Why it matters.** A function's signature is the contract every caller relies on. Loup asks you to write that contract down — parameter types and the return type — and then checks every call against it.

## Declaring a function

A function declaration has the keyword `func`, a name, typed parameters, a return type after a colon, and a body in braces:

```loup
@mode(web.compiled)

func lineTotal(unitPrice: float, quantity: int): float {
    return unitPrice * (quantity as float);
}

func printReceipt(item: string, total: float): void {
    println(i"{$ item $}: {$ total $}");
}

func main(): void {
    let total = lineTotal(2.5, 4);
    printReceipt("Notebooks", total);
}
```

*Listing 4-1: Two functions and the `main` that calls them*

```text
Notebooks: 10
```

A function that returns nothing has the return type `void`; it may end without a `return`, or leave early with a bare `return;`. `main` is the entry point of a `web.compiled` program: it takes no parameters and returns `void` or `int` (Chapter 2).

If you come from Rust or TypeScript, your fingers may type `fn` and `->`. Loup uses `fn` only in function *types* (below), and there is no `->`:

```loup,does_not_compile
@mode(web.compiled)

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

func main(): void {
    println(add(2, 3));
}
```

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

The parser does not know `fn` as a declaration keyword, so it reports generic syntax errors at the start of the line. Write `func add(a: int, b: int): int { … }`.

### The return type is part of the signature

If you leave out the return type, the function returns `void`. The mistake then surfaces where you use the result, not where you declared it:

```loup,does_not_compile
@mode(web.compiled)

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

func main(): void {
    println(add(2, 3));
}
```

```text
main.lpweb:8:13: error[E0001]: argument 1 of 'println': expected 'Any', found 'void'
```

`add(2, 3)` has the type `void`, and `println` cannot print "nothing". The fix is to declare what the function returns: `func add(a: int, b: int): int`.

A function with a return type must return a value on every path. This one forgets zero:

```loup,does_not_compile
@mode(web.compiled)

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

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

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

(The position `1:1` is where Loup 1.25.1 reports it; the function is named in the message.) Add the missing case:

```loup
@mode(web.compiled)

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

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

```text
positive
zero
```

### Arguments are checked against parameter types

Every argument must have the parameter's type. There is no implicit `int` to `float` conversion, so an area function called with whole numbers is rejected:

```loup,does_not_compile
@mode(web.compiled)

func area(width: float, height: float): float {
    return width * height;
}

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

```text
main.lpweb:8:18: error[E0001]: argument 1 of 'area': expected 'float', found 'int'
main.lpweb:8:21: error[E0001]: argument 2 of 'area': expected 'float', found 'int'
```

Pass float literals (`area(3.0, 4.0)`), or convert a variable with `as float`.

## Default parameter values

A parameter may have a default. A call can then leave out that argument:

```loup
@mode(web.compiled)

func greet(name: string, salutation: string = "Hello"): string {
    return i"{$ salutation $}, {$ name $}!";
}

func main(): void {
    println(greet("Alice"));
    println(greet("Bob", "Hi"));
}
```

```text
Hello, Alice!
Hi, Bob!
```

The default expression is evaluated on every call that omits the argument (Language Reference §1-7.1.1, ruling DEF-1). Python programmers know the trap where a default list is shared between calls; in Loup each call gets a fresh one:

```loup
#~ @mode(web.compiled)
#~
func addTag(tag: string, tags: list[string] = []): list[string] {
    var result = tags;
    result.push(tag);
    return result;
}

func main(): void {
    println(addTag("new"));
    println(addTag("sale"));          # not ["new", "sale"]
    println(addTag("sale", ["new"]));
}
```

*Listing 4-2: A default is evaluated on each call*

```text
["new"]
["sale"]
["new", "sale"]
```

Once a parameter has a default, every later parameter needs one too.

## Named arguments

Any argument can be passed by name, as `name = value` or `name: value`; the two spellings mean the same thing. Named arguments let you skip defaults and make long calls readable:

```loup
@mode(web.compiled)

func windowTitle(width: int, height: int, title: string = "Untitled"): string {
    return i"{$ title $} ({$ width $}x{$ height $})";
}

func main(): void {
    println(windowTitle(800, 600));
    println(windowTitle(width = 800, height = 600, title = "Editor"));
    println(windowTitle(title: "Settings", width: 400, height: 300));
}
```

*Listing 4-3: Positional and named arguments*

```text
Untitled (800x600)
Editor (800x600)
Settings (400x300)
```

The number of arguments is checked against the signature:

```loup,does_not_compile
#~ @mode(web.compiled)
#~
func windowTitle(width: int, height: int, title: string = "Untitled"): string {
    return i"{$ title $} ({$ width $}x{$ height $})";
}

func main(): void {
    println(windowTitle(800));
}
```

```text
main.lpweb:8:13: error[E0001]: function 'windowTitle' expects 2 to 3 argument(s), but 1 provided
```

"2 to 3" because `title` has a default and `width` and `height` do not.

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

## Lambdas

A lambda is a function without a name. The expression form `func(params) => expr` returns the expression; the block form has a body and an explicit `return`:

```loup
#~ @mode(web.compiled)
#~
func main(): void {
    let double = func(n: int) => n * 2;
    let clamp = func(n: int, low: int, high: int): int {
        if (n < low) {
            return low;
        }
        if (n > high) {
            return high;
        }
        return n;
    };
    let answer = func() => 42;
    println(double(21));
    println(clamp(150, 0, 100));
    println(answer());
}
```

*Listing 4-4: Expression-bodied and block-bodied lambdas*

```text
42
100
42
```

The Language Reference lets you leave out a lambda's parameter types when the context fixes them, as in `nums.map(func(n) => n * 2)` (§1-7.2). Loup 1.25.1 does not infer them yet; an unannotated parameter becomes `Any`, and arithmetic on it fails:

```loup,does_not_compile
#~ @mode(web.compiled)
#~
func main(): void {
    let prices = [12, 30, 7];
    let doubled = prices.map(func(p) => p * 2);
    println(doubled);
}
```

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

Annotate the parameters (and, for clarity, the result):

```loup
#~ @mode(web.compiled)
#~
func main(): void {
    let prices = [12, 30, 7];
    let doubled = prices.map(func(p: int): int => p * 2);
    println(doubled);
}
```

```text
[24, 60, 14]
```

## Closures

A lambda can use variables from the scope around it. Reading them needs nothing special. *Assigning* to an outer `var` from inside a closure must be declared with `nonlocal` (or `global` for a module-level variable), so that the mutation is visible in the source (Language Reference §1-7.3). Here is a counter factory that forgets:

```loup,does_not_compile
@mode(web.compiled)

func makeCounter(start: int): fn(): int {
    var count = start;
    return func(): int {
        count = count + 1;
        return count;
    };
}

func main(): void {
    let next = makeCounter(10);
    println(next());
}
```

```text
main.lpweb: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)
```

Add `nonlocal count;` as the first statement of the lambda:

```loup
@mode(web.compiled)

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

func main(): void {
    let next = makeCounter(10);
    println(next());
    println(next());
    let other = makeCounter(0);
    println(other());
    println(next());
}
```

*Listing 4-5: A closure that owns its own counter*

```text
11
12
1
13
```

Each call to `makeCounter` creates a new `count`, and the returned closure keeps it alive after `makeCounter` has returned. `next` and `other` do not share state.

## Function types

The type of a function is written `fn(ParamTypes): ReturnType`. Functions are values, so they can be passed, returned and stored, and a type alias keeps long function types readable:

```loup
@mode(web.compiled)

type Discount = fn(float): float;

func applyAll(price: float, rules: list[Discount]): float {
    var result = price;
    for (rule in rules) {
        result = rule(result);
    }
    return result;
}

func tenPercentOff(price: float): float {
    return price * 0.9;
}

func main(): void {
    let minusFive: Discount = func(p: float): float => p - 5.0;
    println(applyAll(100.0, [tenPercentOff, minusFive]));
}
```

*Listing 4-6: Named functions and lambdas as values of one function type*

```text
85
```

A named function such as `tenPercentOff` and a lambda are interchangeable wherever their signatures match.

## Higher-order functions

`list` has `map`, `filter`, `reduce` (also spelled `fold`), `any` and `all`, which take functions. `reduce` takes the starting value first and then the combining function:

```loup
#~ @mode(web.compiled)
#~
func main(): void {
    let orders = [120, 45, 300, 80, 15];

    let large = orders.filter(func(n: int): bool => n >= 100);
    let total = orders.reduce(0, func(acc: int, n: int): int => acc + n);
    let withFee = orders.map(func(n: int): int => n + 5);
    let anyTiny = orders.any(func(n: int): bool => n < 20);
    let largeTotal = orders
        .filter(func(n: int): bool => n >= 100)
        .reduce(0, func(acc: int, n: int): int => acc + n);

    println(large);
    println(total);
    println(withFee);
    println(anyTiny);
    println(largeTotal);
}
```

*Listing 4-7: `filter`, `reduce`, `map` and `any` on a list of order totals*

```text
[120, 300]
560
[125, 50, 305, 85, 20]
true
420
```

Chapter 6 covers the collection methods in full.

## Generic functions

A function can take type parameters in square brackets. `T` below stands for whatever element type the caller's list has:

```loup
@mode(web.compiled)

func first[T](items: list[T]): Option[T] {
    if (items.length() == 0) {
        return None;
    }
    return Some(items[0]);
}

func mapAll[T, R](items: list[T], transform: fn(T): R): list[R] {
    var result: list[R] = [];
    for (item in items) {
        result.push(transform(item));
    }
    return result;
}

func main(): void {
    println(repr(first([3, 4])));
    let empty: list[string] = [];
    println(repr(first(empty)));
    println(mapAll(["a", "bb", "ccc"], func(s: string): int => s.length()));
}
```

*Listing 4-8: Two generic functions*

```text
Some(3)
None
[1, 2, 3]
```

Constraints on type parameters (`T: Comparable[T]`) and the rest of the generics story are in Chapter 11.

## Generator functions

A `gen func` produces a sequence lazily: each `yield` hands one value to the loop that is consuming it, and the function resumes from there on the next iteration (Language Reference §1-7.5). A natural parameter name for a countdown is `from`, but `from` is a keyword:

```loup,does_not_compile
@mode(web.compiled)

gen func countdown(from: int): Iterable[int] {
    var n = from;
    while (n > 0) {
        yield n;
        n = n - 1;
    }
}

func main(): void {
    for (n in countdown(3)) {
        println(n);
    }
}
```

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

Rename the parameter:

```loup
@mode(web.compiled)

gen func countdown(start: int): Iterable[int] {
    var n = start;
    while (n > 0) {
        yield n;
        n = n - 1;
    }
}

func main(): void {
    for (n in countdown(3)) {
        println(n);
    }
}
```

*Listing 4-9: A generator*

```text
3
2
1
```

`async func`, the fourth kind of function, is covered in Chapter 15.

## Recursion

Functions may call themselves, and functions in the same module may call each other regardless of declaration order:

```loup
@mode(web.compiled)

func isEven(n: int): bool {
    if (n == 0) {
        return true;
    }
    return isOdd(n - 1);
}

func isOdd(n: int): bool {
    if (n == 0) {
        return false;
    }
    return isEven(n - 1);
}

func main(): void {
    println(isEven(10));
    println(isOdd(7));
}
```

```text
true
true
```

Loup does not eliminate tail calls. Very deep recursion exhausts the call stack and throws `StackOverflowError`; on the interpreter that happened at a depth of about 64,000 calls. For unbounded depth, use a loop or a `gen func`.

## Property getters and setters

A class can expose a computed value with field syntax using `get` and `set` (Language Reference §1-7.7). Classes are Chapter 9's subject; this is the function side of it:

```loup
@mode(web.compiled)

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;
    }
}

func main(): void {
    let oven = Temperature(200.0);
    println(oven.fahrenheit);
    safe { oven.fahrenheit = 212.0; }
    println(oven.celsius);
}
```

*Listing 4-10: A computed property*

```text
392
100
```

A setter changes the object, so the specification treats it as a `modifier` and requires the call to sit in a `safe { }` block (Chapter 9). Loup 1.25.1 does not check this for setters yet, but write the `safe` block anyway.

## Decorators

> ⚠ **Planned.** The Language Reference defines decorators as functions of the form `func(target: T, ...args): T` that wrap or annotate a declaration (`@logged func square(…)`, §1-7.6). Loup 1.25.1 does not apply them: an `@name` it does not recognise produces the warning `Unknown annotation '@logged'` and the function runs unwrapped. Wrap explicitly instead, as Listing 4-11 does with `withLogging(…)`.

## Visibility: `pub`

A module-level function, type or constant is private to its module unless it is marked `pub` (Language Reference §1-8.2). There are only these two levels; there is no `protected` and no `pub(package)`. Chapter 13 shows `pub` across files.

## A worked example: composable request handlers

The pieces of this chapter combine into a small request pipeline. A handler is a function from `Request` to `Response`; middleware is a function that takes a handler and returns a new one:

```loup
@mode(web.compiled)

struct Request {
    method: string;
    path: string;
}

struct Response {
    status: int;
    body: string;
    headers: dict[string, string];
}

type Handler = fn(Request): Response;

func notFound(req: Request): Response {
    return Response(status = 404, body = "Not Found", headers = {});
}

func routes(req: Request): Response {
    if (req.path == "/") {
        return Response(status = 200, body = "Welcome", headers = {});
    }
    return notFound(req);
}

func withCors(inner: Handler): Handler {
    return func(req: Request): Response {
        let res = inner(req);
        var headers = res.headers;
        headers["access-control-allow-origin"] = "*";
        return res with (headers = headers);
    };
}

func withLogging(inner: Handler): Handler {
    return func(req: Request): Response {
        let res = inner(req);
        println(i"{$ req.method $} {$ req.path $} -> {$ res.status $}");
        return res;
    };
}

func main(): void {
    let app: Handler = withLogging(withCors(routes));
    let home = app(Request(method = "GET", path = "/"));
    println(home.body);
    println(home.headers);
    let missing = app(Request(method = "GET", path = "/admin"));
    println(missing.body);
}
```

*Listing 4-11: Middleware as functions that wrap functions*

```text
GET / -> 200
Welcome
{"access-control-allow-origin": "*"}
GET /admin -> 404
Not Found
```

`struct` values and the `with (…)` copy are explained in Chapter 7. Everything else is functions: `withCors` and `withLogging` each return a closure over the handler they were given, and composing them is ordinary function application.

## Try it yourself

1. Add a `withTiming` middleware to Listing 4-11 that appends an `x-handled-by` header, and put it between the other two.
2. Write `retry(attempts: int, body: fn(): bool): bool` that calls `body` until it returns `true` or the attempts run out.
3. Turn `countdown` from Listing 4-9 into `evens(limit: int)`, a generator of the even numbers below `limit`.

## Summary

- `func name(param: Type): ReturnType { … }` declares a function; leaving out the return type makes it `void`, and a value-returning function must return on every path.
- Arguments are checked against parameter types and counts; there is no implicit `int` to `float` conversion.
- Defaults are evaluated on each call; any argument can be passed by name with `=` or `:`. Rest and keyword-only parameters are planned.
- Lambdas are `func(x: T) => expr` or `func(x: T): R { … }`; in Loup 1.25.1 annotate their parameters.
- Closures read outer variables freely and assign them only after `nonlocal` (or `global`).
- Function types are `fn(A, B): R`; named functions and lambdas are interchangeable values.
- `gen func` produces a lazy sequence with `yield`; reserved words such as `from` cannot name parameters.
- `pub get`/`pub set` define computed properties; decorators are planned.
- Declarations are module-private unless marked `pub`.
