Funktionen verarbeiten ihre Argumente zu einem Ergebnis, das sie beim Aufruf zurückliefern.
## Formen
Funktionen können in verschiedenen Formen definiert werden:
I. Als `function ... end`-Block
```{julia}
function hyp(x,y)
sqrt(x^2+y^2)
end
```
II. Als "Einzeiler"
```{julia}
hyp(x, y) = sqrt(x^2 + y^2)
```
III. Als anonyme Funktionen
```{julia}
(x, y) -> sqrt(x^2 + y^2)
```
### Block-Form und `return`
- Mit `return` wird die Abarbeitung der Funktion beendet und zum aufrufenden Kontext zurückgekehrt.
- Ohne `return` wird der Wert des letzten Ausdrucks als Funktionswert zurückgegeben.
Die beiden Definitionen
```julia
function xsinrecipx(x)
if x == 0
return 0.0
end
return x * sin(1/x)
end
```
und ohne das zweite explizite `return` in der letzten Zeile:
```julia
function xsinrecipx(x)
if x == 0
return 0.0
end
x * sin(1/x)
end
```
sind also äquivalent.
- Eine Funktion, die "nichts" zurückgibt (_void functions_ in der C-Welt), gibt den Wert `nothing` vom Typ `Nothing` zurück. (So wie ein Objekt vom Typ `Bool` die beiden Werte `true` und `false` haben kann, so kann ein Objekt vom Typ `Nothing` nur einen einzigen Wert, eben `nothing`, annehmen.)
- Eine leere `return`-Anweisung ist äquivalent zu `return nothing`.
```{julia}
function fn(x)
println(x)
return
end
a = fn(2)
```
```{julia}
a
```
```{julia}
@show a typeof(a);
```
### Einzeiler-Form
Die Einzeilerform ist eine ganz normale Zuweisung, bei der links eine Funktion steht.
```julia
hyp(x, y) = sqrt(x^2 + y^2)
```
Julia kennt zwei Möglichkeiten, mehrere Anweisungen zu einem Block zusammenzufassen, der an Stelle einer Einzelanweisung stehen kann:
- `begin ... end`-Block
- Eingeklammerte durch Semikolon getrennte Anweisungen.
In beiden Fällen ist der Wert des Blockes der Wert der letzten Anweisung.
Damit funktioniert auch
```julia
hyp(x, y) = (z = x^2; z += y^2; sqrt(z))
```
und
```julia
hyp(x, y) = begin
z = x^2
z += y^2
sqrt(z)
end
```
### Anonyme Funktionen
Anonyme FUnktionen kann man der Anonymität entreisen, indem man ihnen einen Namen zuweist.
```julia
hyp = (x,y) -> sqrt(x^2 + y^2)
```
Ihre eigentliche Anwendung ist aber im Aufruf einer *(higher order)* Funktion, die eine Funktion als Argument erwartet.
Typische Anwendungen sind `map(f, collection)`, welches eine Funktion auf alle Elemente einer Kollektion anwendet. In Julia funktioniert auch `map(f(x,y), collection1, collection2)`:
Ein weiteres Beispiel ist `filter(test, collection)`, wobei ein Test eine Funktion ist, die ein `Bool` zurückgibt.
```{julia}
filter(x -> ( x%3 == 0 && x%5 == 0), 1:100 )
```
## Argumentübergabe
- Beim Funktionsaufruf werden von den als Funktionsargumente zu übergebenden Objekten keine Kopien gemacht. Die Variablen in der Funktion verweisen auf die Originalobjekte. Julia nennt dieses Konzept _pass_by_sharing_.
- Funktionen können also ihre Argumente wirksam modifizieren, falls es sich um _mutable_ Objekte, wie z.B. `Vector`, `Array` handelt.
- Es ist eine Konvention in Julia, dass die Namen von solchen argumentmodifizierenden Funktionen mit einem Ausrufungszeichen enden. Weiterhin steht dann üblicherweise das Argument, das modifiziert wird, an erster Stelle und es ist auch der Rückgabewert der Funktion.
```{julia}
V = [1, 2, 3]
W = fill!(V, 17)
# '===' ist Test auf Identität
@show V W V===W; # V und W benennen dasselbe Objekt
```
```{julia}
function fill_first!(V, x)
V[1] = x
return V
end
U = fill_first!(V, 42)
@show V U V===U;
```
## Varianten von Funktionsargumenten
- Es gibt Positionsargumente (1. Argument, 2. Argument, ....) und _keyword_-Argumente, die beim Aufruf durch ihren Namen angesprochen werden müssen.
- Sowohl Positions- als auch _keyword_-Argumente können _default_-Werte haben. Beim Aufruf können diese Argumente weggelassen werden.
- Die Reihenfolge der Deklaration muss sein:
1. Positionsargumente ohne Defaultwert,
2. Positionsargumente mit Defaultwert,
3. --- Semikolon ---,
4. kommagetrennte Liste der Keywordargumente (mit oder ohne Defaultwert)
- Beim Aufruf können _keyword_-Argumente an beliebigen Stellen in beliebiger Reihenfolge stehen. Man kann sie wieder durch ein Semikolon von den Positionsargumenten abtrennen, muss aber nicht.
```{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)
```
Eine Funktion nur mit _keyword_-Argumenten wird so deklariert:
```{julia}
fkw(; x=10, y) = println("x=$x, y=$y")
fkw(y=2)
```
## Funktionen sind ganz normale Objekte
- Sie können zugewiesen werden
```{julia}
f2 = sqrt
f2(2)
```
- Sie können als Argumente an Funktionen übergeben werden.
```{julia}
# sehr naive numerische Integration
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, π)
```
- Sie können von Funktionen erzeugt und als Ergebnis `return`t werden.
```{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)
```
Die obige Funktion `generate_add_func()` lässt sich auch kürzer definieren. Der innere Funktionsname `addx()` ist sowieso lokal und außerhalb nicht verfügbar. Also kann man eine anonyme Funktion verwenden.
```{julia}
generate_add_func(x) = y -> x + y
```
## Zusammensetzung von Funktionen: die Operatoren $\circ$ und `|>`
- Die Zusammensetzung _(composition)_ von Funktionen kann auch mit dem Operator $\circ$ (`\circ + Tab`) geschrieben werden
- Es gibt auch einen Operator, mit dem Funktionen "von rechts" wirken und zusammengesetzt werden können _(piping)_
```{julia}
25 |> sqrt
```
```{julia}
1:10 |> sum |> sqrt
```
- Natürlich kann man auch diese Operatoren 'broadcasten' (s. @sec-broadcast). Hier wirkt ein Vektor von Funktionen elementweise auf einen Vektor von Argumenten:
(Der Doppelpunkt vor einer Variablen macht diese zu einem Symbol.)
:::{.callout-note}
Für diese Funktionen kann man eigene Methoden implementieren. Zum Beispiel könnten bei einem eigenen Typ das Setzen eines Feldes (`setproperty!()`) die Gültigkeit des Wertes prüfen oder weitere Aktionen veranlassen.
Prinzipiell können `get/setproperty` auch Dinge tun, die gar nichts mit einem tatsächlich vorhandenen Feld der Struktur zu tun haben.
:::
## Update-Form
Alle arithmetischen Infix-Operatoren haben eine update-Form: Der Ausdruck
```julia
x = x ⊙ y
```
kann auch geschrieben werden als
```julia
x ⊙= y
```
Beide Formen sind semantisch äquivalent. Insbesondere wird in beiden Formen der Variablen `x` ein auf der rechten Seite geschaffenes neues Objekt zugewiesen.
Ein Speicherplatz- und Zeit-sparendes __in-place-update__ eines Arrays/Vektors/Matrix ist möglich entweder durch explizite Indizierung
```julia
for i in eachindex(x)
x[i] += y[i]
end
```
oder durch die dazu semantisch äquivalente _broadcast_-Form (s. @sec-broadcast):
```julia
x .= x .+ y
```
## Vorrang und Assoziativität von Operatoren {#sec-vorrang}
Natürlich kann man die Assoziativität in Julia auch abfragen. Die entsprechenden Funktionen werden nicht explizit aus dem `Base`-Modul exportiert, deshalb muss man den Modulnamen beim Aufruf angeben.
- unäre Operatoren, also insbesondere `+` und `-` als Vorzeichen
- _juxtaposition_, also Zahlen direkt vor Variablen oder Klammern ohne `*`-Symbol
Beide haben Vorrang noch vor Multiplikation und Division.
:::{.callout-important}
Damit ändert sich die Bedeutung von Ausdrücken, wenn man _juxtaposition_ anwendet:
```{julia}
1/2*π, 1/2π
```
:::
- Im Vergleich zum Potenzoperator `^` gilt (s. [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.
Beispiele:
```{julia}
-2^2 # -(2^2)
```
```{julia}
x = 5
2x^2 # 2(x^2)
```
```{julia}
2^-2 # 2^(-2)
```
```{julia}
2^2x # 2^(2x)
```
- Funktionsanwendung `f(...)` hat Vorrang vor allen Operatoren
```{julia}
sin(x)^2 === (sin(x))^2 # nicht sin(x^2)
```
### Zusätzliche Operatoren
Der [Julia-Parser](https://github.com/JuliaLang/julia/blob/master/src/julia-parser.scm#L13-L31) definiert für zahlreiche Unicode-Zeichen einen Vorrang auf Vorrat, so dass diese Zeichen von Paketen und selbstgeschriebenem Code als Operatoren benutzt werden können.