# Function

A function is a block that takes an "argument", processes it, and returns it as a "return value". It is defined as follows.

```python
add x, y = x + y
# or
add(x, y) = x + y
```

The names specified after a function name are called parameters.
In contrast, the objects passed to a function are called arguments.
The function `add` is a function that takes `x` and `y` as parameters and returns the sum of them, `x + y`.
The defined function can be called (applied/invoked) as follows.

```python
add 1, 2
# or
add(1, 2)
```

## Colon application style

Functions are invoked like `f x, y, ...`, but if there are too many arguments for a single line, they can be applied using `:` (colon).

```python
f some_long_name_variable_1 + some_long_name_variable_2, some_long_name_variable_3 * some_long_name_variable_4
```

```python
f some_long_name_variable_1 + some_long_name_variable_2:
    some_long_name_variable_3 * some_long_name_variable_4
```

Codes above mean the same thing. This style is also useful when using `if` functions, for example.

```python
result = if Bool.sample!():
    do:
        log "True was chosen"
        1
    do:
        log "False was chosen"
        0
```

After `:`, no code other than comments may be written, and must always be on a new line.
Also, you cannot use `:` immediately after a function. Only `do` and `do!` can do this.

```python
# NG
f:
    x
    y
```

## Keyword Arguments

If a function is defined with a large number of parameters, there is a danger of passing the arguments in the wrong order.
In such cases, it is safe to call the function using keyword arguments.

```python
f x, y, z, w, v, u: Int = ...
```

The functions defined above have many arguments and are arranged in a confusing order. You should not create such a function, but you may encounter such code when using code written by others. Therefore, we use keyword arguments. If you use keyword arguments, the values are passed from the name to the correct argument, even if they are in the wrong order.

```python
f u: 6, v: 5, w: 4, x: 1, y: 2, z: 3
```

Note that keyword arguments and a new line immediately after the `:` are considered a colon-call style.

```python
# means `f(x: y)`
f x: y

# means `f(x, y)`
f x:
    y
```

## Default parameters

Default parameters are used when some parameters are mostly fixed and you want to be able to omit them.

Default parameters are specified by `:=`(walrus operator). If `base` is not specified, assign `math.E` to `base`.

```python
math_log x: Ratio, base := math.E = ...

assert math_log(100, 10) == 2
assert math_log(100) == math_log(100, math.E)
```

Note that there is a distinction between specifying no argument and assigning `None`.

```python
p! x := 0 = print!
p!(2) # 2
p!() # 0
p!(None) # None
```

Can also be used with type specification and patterns.

```python
math_log x, base: Ratio := math.E = ...
f [x, y] := [1, 2] = ...
```

However, within the default arguments, it is not possible to call the procedures (described later) or assign mutable objects.

```python
f x := p! 1 = ... # NG
```

Also, the argument just defined cannot be used as the value passed to the default argument.

```python
f x := 1, y := x = ... # NG
```

## Variable-length arguments

The `log` function, which outputs a log (record) of its arguments, can take any number of arguments.

```python
log "Hello", "World", "!" # Hello World !
```

To define such a function, add `...` to a parameter. This way, the function receives arguments as a variable-length array.

```python
f ...x =
    for x, i ->
        log i

# x == [1, 2, 3, 4, 5]
f 1, 2, 3, 4, 5
```

## Function definition with multiple patterns

```python
fib n: Nat =
    match n:
        0 -> 0
        1 -> 1
        n -> fib(n - 1) + fib(n - 2)
```

Functions like the one above, where `match` appears directly under the definition, can be rewritten as follows.

```python
fib 0 = 0
fib 1 = 1
fib(n: Nat): Nat = fib(n - 1) + fib(n - 2)
```

Note that a function definition with multiple patterns is not so-called overloading (multiple definition); a function has only a single definition. In the example above, `n` must be of the same type as `0` or `1`. Also, as with `match`, pattern matching is done from top to bottom.

If instances of different classes are mixed, the last definition must specify that the function argument is of type `Or`.

```python
f "aa" = ...
f 1 = ...
# `f x = ... ` is invalid
f x: Int or Str = ...
```

Also, like `match`, it must also be exhaustive.

```python
fib 0 = 0
fib 1 = 1
# PatternError: pattern of fib's parameter is not exhaustive
```

However, it can be made exhaustive by explicitly specifying the type using the [refinement type](./type/12_refinement.md) described later.

```python
fib: 0..1 -> 0..1
fib 0 = 0
fib 1 = 1
# OK
```

## Recursive functions

A recursive function is a function that includes itself in its definition.

As a simple example, let us define a function `factorial` that performs a factorial calculation. Factorial is a computation that "multiplies all positive numbers less than or equal to".
The factorial of 5 is `5*4*3*2*1 == 120`.

```python
factorial 0 = 1
factorial 1 = 1
factorial(n: Nat): Nat = n * factorial(n - 1)
```

First, from the definition of factorial, the factorial of 0 and 1 are both 1.
In turn, the factorial of 2 is `2*1 == 2`, the factorial of 3 is `3*2*1 == 6`, and the factorial of 4 is `4*3*2*1 == 24`.
If we look closely, we can see that the factorial of a number n is the factorial of the preceding number n-1 multiplied by n.
Putting this into code, we get `n * factorial(n - 1)`.
Since the definition of `factorial` contains itself, `factorial` is a recursive function.

As a reminder, if you do not add a type specification, it is inferred like this.

```python
factorial: |T <: Sub(Int, T) and Mul(Int, Int) and Eq(Int)| T -> Int
factorial 0 = 1
factorial 1 = 1
factorial n = n * factorial(n - 1)
```

However, even if you can reason about it, you should explicitly specify the type of the recursive function. In the example above, a code like ``factorial(-1)`` would work, but

```python
factorial(-1) == -1 * factorial(-2) == -1 * -2 * factorial(-3) == ...
```

and this computation does not stop. Recursive functions must carefully define the range of values or you may end up in an infinite loop.
So the type specification also helps to avoid accepting unexpected values.

## Compile-time functions

A function name begins with an uppercase letter to indicate a compile-time function. User-defined compile-time functions must have all arguments as constants and must specify their types.
Compile-time functions are limited in what they can do. Only constant expressions can be used in compile-time functions, i.e., only some operators (such as quadrature, comparison, and type construction operations) and compile-time functions. Arguments to be passed must also be constant expressions.
In return, the advantage is that the computation can be done at compile time.

```python
Add(X, Y: Nat): Nat = X + Y
assert Add(1, 2) == 3

Factorial 0 = 1
Factorial(X: Nat): Nat = X * Factorial(X - 1)
assert Factorial(10) == 3628800

math = import "math"
Sin X = math.sin X # ConstantError: this function is not computable at compile time
```

Compile-time functions are also used in polymorphic type definitions.

```python
Option T: Type = T or NoneType
Option: Type -> Type
```

## Appendix: Function Comparison

Erg does not define `==` for functions. This is because there is no structural equivalence algorithm for functions in general.

```python
f = x: Int -> (x + 1)**2
g = x: Int -> x**2 + 2x + 1

assert f == g # TypeError: cannot compare functions
```

Although `f` and `g` always return the same result, it is extremely difficult to make that determination. We have to teach algebra to the compiler.
So Erg gives up on function comparisons entirely, and `(x -> x) == (x -> x)` also results in a compile error. This is a different specification from Python and should be noted.

```python
# Python, weird example
f = lambda x: x
assert f == f
assert (lambda x: x) ! = (lambda x: x)
```

## Appendix2: ()-completion

```python
f x: Object = ...
# will be completed to
f(x: Object) = ...

f a
# will be completed to
f(a)

f a, b # TypeError: f() takes 1 positional argument but 2 were given
f(a, b) # TypeError: f() takes 1 positional argument but 2 were given
f((a, b)) # OK
```

The function type `T -> U` is actually the syntax sugar of `(T,) -> U`.

<p align='center'>
    <a href='./03_declaration.md'>Previous</a> | <a href='./05_builtin_funcs.md'>Next</a>
</p>
