---
engine: julia
---

# Functions and Operators

```{julia}
#| error: false
#| echo: false
#| output: false
using InteractiveUtils

#struct M a::Int end; x = M(22); @show x 
#should not print "Main.Notebook.M(22)" but only "M(22)"
function Base.show(io::IO, x::T) where T
    if parentmodule(T) == @__MODULE__
        # Print "TypeName(fields...)" without module prefix
        print(io, nameof(T), "(")
        fields = fieldnames(T)
        for (i, f) in enumerate(fields)
            print(io, getfield(x, f))
            i < length(fields) && print(io, ", ")
        end
        print(io, ")")
    else
        invoke(Base.show, Tuple{IO, Any}, io, x)
    end
end
```


Functions process their arguments to produce and return a result when called.

## Forms of function definitions


I. Block form: `function ... end`
```{julia}
function hyp(x,y)
    sqrt(x^2+y^2)
end
```
II. Single-line form
```{julia}
hyp(x, y) = sqrt(x^2 + y^2)
```
III. Anonymous functions

```{julia}
(x, y) -> sqrt(x^2 + y^2)
```

   
### Block Form and `return` Statement


- With `return`, function execution terminates and control returns to the calling context.
- Without `return`, the value of the last expression is returned as the function value.

The two definitions

```julia
function xsinrecipx(x)
    if x == 0 
        return 0.0
    end 
    return x * sin(1/x)
end
```
and the equivalent version without explicit `return` in the last line:

```julia
function xsinrecipx(x)
    if x == 0 
        return 0.0
    end 
    x * sin(1/x)
end
```

are therefore equivalent.


- A function that returns `nothing` (_void functions_ in C) returns a `nothing` value of type `Nothing`. (Just as a `Bool` object has two values, `true` and `false`, a `Nothing` object has only one: `nothing`.)
- An empty `return` statement is equivalent to `return nothing`. 


```{julia}
function fn(x)
    println(x)
    return 
end

a = fn(2)
```


```{julia}
a
```


```{julia}
@show a typeof(a);
```


### Single-liner Form

The single-liner form looks like a simple assignment:

```julia
hyp(x, y) = sqrt(x^2 + y^2)
```

Julia provides two ways to combine multiple statements into a block that can stand in place of a single statement:

- `begin ... end` block
- Parenthesized statements separated by semicolons.

In both cases, the value of the block is the value of the last statement.

Thus, the following also works:
```julia
hyp(x, y) = (z = x^2; z += y^2; sqrt(z))
```

and
```julia
hyp(x, y) = begin 
                z = x^2
                z += y^2 
                sqrt(z) 
            end
```

### Anonymous Functions

Anonymous functions can be "rescued from anonymity" by assigning them a name:
```julia
hyp = (x,y) -> sqrt(x^2 + y^2)
```
Their actual application is in calling a *(higher order)* function that expects a function as an argument. 

Typical applications include `map(f, collection)`, which applies a function to every element of a collection. Julia also supports `map(f, collection1, collection2)` with multiple collections:

```{julia}
map( (x,y) -> sqrt(x^2 + y^2), [3, 5, 8], [4, 12, 15])
```

```{julia}
map( x->3x^3, 1:8 )
```

Another example is `filter(test, collection)`, where a test is a function that returns a `Bool`.
```{julia}
filter(x -> ( x%3 == 0 && x%5 == 0), 1:100  )
```

## Argument Passing


- When calling a function, Julia does not copy objects passed as arguments. Function arguments refer to the original objects. Julia calls this concept _pass_by_sharing_.  
- Consequently, functions can modify their arguments if they are mutable (e.g., `Vector` or `Array`).
- By convention, functions that modify their arguments end with an exclamation mark. The modified argument is typically the first argument and is also returned. 

```{julia}
V = [1, 2, 3]

W = fill!(V, 17)
                        # '===' tests for identity
@show  V  W  V===W;     # V and W refer to the same object 
```

```{julia}
function fill_first!(V, x)
        V[1] = x
        return V    
end                     

U = fill_first!(V, 42)

@show V   U   V===U;
```



## Function Argument Variants

- There are positional arguments (1st argument, 2nd argument, ...) and _keyword_ arguments, which must be addressed by name when calling. 
- Both positional and _keyword_ arguments can have _default_ values. These arguments can be omitted when calling.
- The order of declaration must be:
    1. Positional arguments without default values, 
    2. Positional arguments with default values,
    3.   --- semicolon ---,
    4. comma-separated list of keyword arguments (with or without default values) 
- When calling, keyword arguments can appear in any order at any position. They can be separated from positional arguments with a semicolon, but this is optional.

```{julia}
fa(x, y=42; a) = println("x=$x, y=$y, a=$a")

fa(6, a=4, 7)
fa(6, 7; a=4)
fa(a=-2, 6)
```

A function with only _keyword_ arguments is declared as follows:

```{julia}
fkw(; x=10, y) = println("x=$x, y=$y")

fkw(y=2)
```



## Functions are just Objects

 - Functions can be assigned to variables


```{julia}
f2 = sqrt
f2(2)
```


- Functions can be passed as arguments to other functions.


```{julia}
# naive Riemann integration example

function Riemann_integrate(f, a, b; NInter=1000)
    delta = (b-a)/NInter
    s = 0
    for i in 0:NInter-1
        s += delta * f(a + delta/2 + i * delta)
    end
    return s
end


Riemann_integrate(sin, 0, π)
```


- They can be created by functions and returned as results.



```{julia}
function generate_add_func(x)
    function addx(y)
        return x+y
    end
    return addx
end
```


```{julia}
h =  generate_add_func(4)
```


```{julia}
h(1)
```


```{julia}
h(2), h(10)
```

The above function `generate_add_func()` can also be defined more briefly. The inner function name `addx` is local and inaccessible outside. An anonymous function can be used instead.

```{julia}
generate_add_func(x) = y -> x + y
```


## Function Composition: the Operators $\circ$ and `|>`

- Function composition can also be written with the $\circ$ operator (`\circ + Tab`)

$$(f\circ g)(x) = f(g(x))$$


```{julia}
(sqrt ∘ + )(9, 16)
```


```{julia}
f = cos ∘ sin ∘ (x->2x)
f(.2)
```


```{julia}
@show map(uppercase ∘ first, ["one", "a", "green", "leaves"]);
```


- There is also an operator with which functions can act "from the right" and be composed (_piping_)


```{julia}
25 |> sqrt
```


```{julia}
1:10 |> sum |> sqrt
```


- These operators can also be broadcast (see @sec-broadcast). A vector of functions is applied element-wise to a vector of arguments:

```{julia}
["a", "list", "of", "strings"] .|> [length, uppercase, reverse, titlecase]
```

## The `do` Notation  {#sec-do}

A syntactic peculiarity for defining anonymous functions as arguments of other functions is the `do` notation.

Let `higherfunc(f, a, ...)` be a function whose first argument is a function. 

The function can be called without the first argument, with the function body defined in a following `do` block:

```julia
higherfunc(a, b) do x, y
   body of f(x,y)
end
```

Using `Riemann_integrate()` as an example, this looks like this:


```{julia}
# this is the same as Riemann_integrate(x->x^2, 0, 2)

Riemann_integrate(0, 2) do x x^2 end
```


The `do` notation is especially useful for complex function bodies, such as this integrand defined in multiple steps:
```{julia}
r = Riemann_integrate(0, π) do x
        z1 = sin(x)
        z2 = log(1+x)
        if x > 1 
            return z1^2
        else
            return 1/z2^2
        end
    end
```

## Function-like Objects

By defining a method for a type, objects become *callable* like functions.

```{julia}
# struct stores coefficients of a second-degree polynomial
struct Poly2Grad 
    a0::Float64
    a1::Float64
    a2::Float64
end

p1 = Poly2Grad(2,5,1)
p2 = Poly2Grad(3,1,-0.4)
```

The following method makes this structure callable:
```{julia}
function (p::Poly2Grad)(x)
    p.a2 * x^2 + p.a1 * x + p.a0
end
```
Objects can now be used like functions:

```{julia}
@show p2(5)  p1(-0.7)  p1;
```



## Operators and Special Forms

- Infix operators such as `+`, `*`, `>`, `∈` are functions.



```{julia}
+(3, 7)
```


```{julia}
f = +
```


```{julia}
f(3, 7)
```

- Constructions like `x[i]`, `a.x`, `[x; y]` are converted by the parser to function calls.

:::{.narrow}

|            |               |
| :-:        | :------------ |
| x[i]   |    getindex(x, i) |
| x[i] = z    |  setindex!(x, z, i) |
| a.x      |     getproperty(a, :x)   | 
| a.x = z |  setproperty!(a, :x, z)   |
| [x; y;...]   | vcat(x, y, ...)     |
  :Special Forms [(selection)](https://docs.julialang.org/en/v1/manual/functions/#Operators-With-Special-Names)

:::

(The colon before a variable makes it into a symbol.)

:::{.callout-note}
For these functions, too, van be extended/overwritten by new methods. For example, for a custom type, setting a field (`setproperty!()`) could check the validity of the value or trigger further actions. 

In principle, `get/setproperty` can also do things that have nothing to do with an actually existing field of the structure. 
:::

## Update Form

All arithmetic infix operators have an update form: The expression
```julia
x = x ⊙ y
```
can also be written as
```julia
x ⊙= y
```

Both forms are semantically equivalent: a new object created on the right is assigned to `x`. 

Memory- and time-efficient *in-place updates* of arrays use explicit indexing:
```julia
for i in eachindex(x)
    x[i] += y[i]
end
```
or semantically equivalent broadcast form (see @sec-broadcast):
```julia
x .= x .+ y
```



## Operator Precedence and Associativity {#sec-vorrang}

Expressions like

```{julia}
 -2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2
```

are converted by the parser into a tree structure:
```{julia}
using TreeView

walk_tree(Meta.parse("-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2"))
```


- Expression evaluation is governed by 
    - precedence and 
    - associativity.
- Precedence determines which operators bind more tightly, such as multiplication before addition.
- Associativity determines the evaluation order for operators of equal precedence.
- [Complete documentation](https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity)

### Associativity

Addition/subtraction and multiplication/division have equal precedence and are left-associative (evaluated left-to-right):

```{julia}
200/5/2      # evaluated left to right as (200/5)/2 
```


```{julia}
200/2*5      # evaluated left to right as (200/2)*5
```

Assignments like `=`, `+=`, `*=`,... are of equal rank and right-associative.

```{julia}
x = 1
y = 10

# evaluated right to left:  x += (y += (z = (a = 20)))

x += y +=  z = a = 20 

@show x y z a;
```

Julia provides functions to query associativity. These functions are not exported from `Base`, so the module name must be specified. 


```{julia}
for i in (:/, :+=, :(=), :^)
    a = Base.operator_associativity(i)
    println("Operation $i is  $(a)-associative")
end
```

Thus, the power operator is right-associative:
```{julia}
2^3^2    # right-associative,  = 2^(3^2) 
```

### Precedence

- Julia assigns operator precedence levels from 1 to 17: 



```{julia}
for i in (:+, :-, :*, :/, :^, :(=))
    p = Base.operator_precedence(i)
    println("Precedence of  $i = $p") 
end
```


- Precedence 11 < 12 explains why multiplication/division bind tighter than addition/subtraction.
- The power operator `^` has higher precedence.
- Assignments have the lowest precedence.


```{julia}
#   assignment has smallest precedence, therefore evaluation as  x = (3 < 4)

x = 3 < 4     
x
```


```{julia}
(y = 3) < 4   # parentheses override any precedence
y
```

Returning to the example above:

```{julia}
-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2
```


```{julia}
for i ∈ (:^, :+, :/, :(==), :&&, :>, :|| )
    print(i, " ")
    println(Base.operator_precedence(i))
end
```
These rules evaluate the expression as:

```{julia}
((-(2^3)+((500/2)/10)==8) && (13 > (7 + 1))) || (9 < 2)
```
(as shown in the  parse tree above).

So the precedence is:

> Power > Multiplication/Division > Addition/Subtraction > Comparisons > logical && > logical || > assignment

Thus, an expression like 
```julia
 a = x <= y + z && x > z/2 
``` 
is sensibly evaluated as `a = ((x <= (y+z)) && (x < (z/2)))`


- A special case is still

    - unary operators, in particular `+` and `-` as signs  
    - _juxtaposition_, i.e., numbers directly before variables or parentheses without `*` symbol
    
    Both have precedence even before multiplication and division. 

:::{.callout-important}
Therefore, the meaning of expressions changes when one applies _juxtaposition_:
```{julia}
1/2*π,  1/2π
```   
:::   
   
- Compared to the power operator `^` (see [https://discourse.julialang.org/t/confused-about-operator-precedence-for-2-3x/8214/7](https://discourse.julialang.org/t/confused-about-operator-precedence-for-2-3x/8214/7) ):
   
   >  Unary operators, including juxtaposition, bind tighter than ^ on the right but looser on the left.

Examples:
```{julia}
-2^2    # -(2^2)
```


```{julia}
x = 5
2x^2    # 2(x^2)
```


```{julia}
2^-2    # 2^(-2)
```


```{julia}
2^2x  # 2^(2x)
```


- Function application `f(...)` has precedence over all operators


```{julia}
sin(x)^2 === (sin(x))^2   # not sin(x^2)
```

### Additional Operators

The [Julia parser](https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm#L13-L31) assigns precedence to numerous Unicode characters in advance, so that these characters can be used as operators by packages and self-written code.  

Thus, for example,
```julia
 ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛
```

have precedence 12 like multiplication/division (and are left-associative like these) 
and for example
```julia
  ⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗  
```
have precedence 11 like addition/subtraction.

