function hyp(x,y)
sqrt(x^2+y^2)
endhyp (generic function with 1 method)
Funktionen verarbeiten ihre Argumente zu einem Ergebnis, das sie beim Aufruf zurückliefern.
Funktionen können in verschiedenen Formen definiert werden:
function ... end-Blockfunction hyp(x,y)
sqrt(x^2+y^2)
endhyp (generic function with 1 method)
hyp(x, y) = sqrt(x^2 + y^2)hyp (generic function with 1 method)
(x, y) -> sqrt(x^2 + y^2)#1 (generic function with 1 method)
returnreturn wird die Abarbeitung der Funktion beendet und zum aufrufenden Kontext zurückgekehrt.return wird der Wert des letzten Ausdrucks als Funktionswert zurückgegeben.Die beiden Definitionen
function xsinrecipx(x)
if x == 0
return 0.0
end
return x * sin(1/x)
endund ohne das zweite explizite return in der letzten Zeile:
function xsinrecipx(x)
if x == 0
return 0.0
end
x * sin(1/x)
endsind also äquivalent.
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.)return-Anweisung ist äquivalent zu return nothing.function fn(x)
println(x)
return
end
a = fn(2)2
a@show a typeof(a);a = nothing
typeof(a) = Nothing
Die Einzeilerform ist eine ganz normale Zuweisung, bei der links eine Funktion steht.
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-BlockIn beiden Fällen ist der Wert des Blockes der Wert der letzten Anweisung.
Damit funktioniert auch
hyp(x, y) = (z = x^2; z += y^2; sqrt(z))und
hyp(x, y) = begin
z = x^2
z += y^2
sqrt(z)
endAnonyme FUnktionen kann man der Anonymität entreisen, indem man ihnen einen Namen zuweist.
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):
map( (x,y) -> sqrt(x^2 + y^2), [3, 5, 8], [4, 12, 15])3-element Vector{Float64}:
5.0
13.0
17.0
map( x->3x^3, 1:8 )8-element Vector{Int64}:
3
24
81
192
375
648
1029
1536
Ein weiteres Beispiel ist filter(test, collection), wobei ein Test eine Funktion ist, die ein Bool zurückgibt.
filter(x -> ( x%3 == 0 && x%5 == 0), 1:100 )6-element Vector{Int64}:
15
30
45
60
75
90
Vector, Array handelt.V = [1, 2, 3]
W = fill!(V, 17)
# '===' ist Test auf Identität
@show V W V===W; # V und W benennen dasselbe ObjektV = [17, 17, 17]
W = [17, 17, 17]
V === W = true
function fill_first!(V, x)
V[1] = x
return V
end
U = fill_first!(V, 42)
@show V U V===U;V = [42, 17, 17]
U = [42, 17, 17]
V === U = true
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)x=6, y=7, a=4
x=6, y=7, a=4
x=6, y=42, a=-2
Eine Funktion nur mit keyword-Argumenten wird so deklariert:
fkw(; x=10, y) = println("x=$x, y=$y")
fkw(y=2)x=10, y=2
f2 = sqrt
f2(2)1.4142135623730951
# 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, π)2.0000008224672694
returnt werden.function generate_add_func(x)
function addx(y)
return x+y
end
return addx
endgenerate_add_func (generic function with 1 method)
h = generate_add_func(4)(::Main.Notebook.var"#addx#12"{Int64}) (generic function with 1 method)
h(1)5
h(2), h(10)(6, 14)
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.
generate_add_func(x) = y -> x + ygenerate_add_func (generic function with 1 method)
|>\circ + Tab) geschrieben werden\[(f\circ g)(x) = f(g(x))\]
(sqrt ∘ + )(9, 16)5.0
f = cos ∘ sin ∘ (x->2x)
f(.2)0.9251300429004277
@show map(uppercase ∘ first, ["ein", "paar", "grüne", "Blätter"]);map(uppercase ∘ first, ["ein", "paar", "grüne", "Blätter"]) = ['E', 'P', 'G', 'B']
25 |> sqrt5.0
1:10 |> sum |> sqrt7.416198487095663
["a", "list", "of", "strings"] .|> [length, uppercase, reverse, titlecase]4-element Vector{Any}:
1
"LIST"
"fo"
"Strings"
do-NotationEine syntaktische Besonderheit zur Definition anonymer Funktionen als Argumente anderer Funktionen ist die do-Notation.
Sei higherfunc(f,a,...) eine Funktion, deren 1. Argument eine Funktion ist.
Dann kann man higherfunc() auch ohne erstes Argument aufrufen und statt dessen die Funktion in einem unmittelbar folgenden do-Block definieren:
higherfunc(a, b) do x, y
Körper von f(x,y)
endAm Beispiel von Riemann_integrate() sieht das so aus:
# das ist dasselbe wie Riemann_integrate(x->x^2, 0, 2)
Riemann_integrate(0, 2) do x x^2 end2.6666659999999993
Der Sinn besteht natürlich in der Anwendung mit komplexeren Funktionen, wie diesem aus zwei Teilstücken zusammengesetzten Integranden:
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
end1578.9022037353475
Durch Definition einer geeigneten Methode für einen Typ kann man beliebige Objekte callable machen, d.h., sie anschließend wie Funktionen aufrufen.
# struct speichert die Koeffiziente eines Polynoms 2. Grades
struct Poly2Grad
a0::Float64
a1::Float64
a2::Float64
end
p1 = Poly2Grad(2,5,1)
p2 = Poly2Grad(3,1,-0.4)Poly2Grad(3.0, 1.0, -0.4)
Die folgende Methode macht diese Struktur callable.
function (p::Poly2Grad)(x)
p.a2 * x^2 + p.a1 * x + p.a0
endJetzt kann man die Objekte, wenn gewünscht, auch wie Funktionen verwenden.
@show p2(5) p1(-0.7) p1;p2(5) = -2.0
p1(-0.7) = -1.0100000000000002
p1 = Main.Notebook.Poly2Grad(2.0, 5.0, 1.0)
+,*,>,∈,... sind Funktionen.+(3, 7)10
f = ++ (generic function with 198 methods)
f(3, 7)10
x[i], a.x, [x; y] werden vom Parser zu Funktionsaufrufen umgewandelt.| 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, …) |
(Der Doppelpunkt vor einer Variablen macht diese zu einem Symbol.)
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.
Alle arithmetischen Infix-Operatoren haben eine update-Form: Der Ausdruck
x = x ⊙ ykann auch geschrieben werden als
x ⊙= yBeide 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
for i in eachindex(x)
x[i] += y[i]
endoder durch die dazu semantisch äquivalente broadcast-Form (s. Kapitel 12.7):
x .= x .+ yZu berechnende Ausdrücke
-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2false
werden vom Parser in eine Baumstruktur überführt.
using TreeView
walk_tree(Meta.parse("-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2"))Sowohl Addition und Subtraktion als auch Multiplikation und Divison sind jeweils gleichrangig und linksassoziativ, d.h. es wird von links ausgewertet.
200/5/2 # wird von links ausgewertet als (200/5)/220.0
200/2*5 # wird von links ausgewertet als (200/2)*5500.0
Zuweisungen wie =, +=, *=,… sind gleichrangig und rechtsassoziativ.
x = 1
y = 10
# wird von rechts ausgewertet: x += (y += (z = (a = 20)))
x += y += z = a = 20
@show x y z a;x = 31
y = 30
z = 20
a = 20
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.
for i in (:/, :+=, :(=), :^)
a = Base.operator_associativity(i)
println("Operation $i is $(a)-assoziative")
endOperation / is left-assoziative
Operation += is right-assoziative
Operation = is right-assoziative
Operation ^ is right-assoziative
Also ist der Potenzoperator rechtsassoziativ.
2^3^2 # rechtsassoziativ, = 2^(3^2)512
for i in (:+, :-, :*, :/, :^, :(=))
p = Base.operator_precedence(i)
println("Vorrang von $i = $p")
endVorrang von + = 11
Vorrang von - = 11
Vorrang von * = 12
Vorrang von / = 12
Vorrang von ^ = 15
Vorrang von = = 1
^ hat eine höhere precedence.# Zuweisung hat kleinsten Vorrang, daher Auswertung als x = (3 < 4)
x = 3 < 4
xtrue
(y = 3) < 4 # Klammern schlagen natürlich jeden Vorrang
y3
Nochmal zum Beispiel vom Anfang von Kapitel 10.10:
-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2false
for i ∈ (:^, :+, :/, :(==), :&&, :>, :|| )
print(i, " ")
println(Base.operator_precedence(i))
end^ 15
+ 11
/ 12
== 7
&& 6
> 7
|| 5
Nach diesen Vorrangregeln wird der Beispielausdruck also wie folgt ausgewertet:
((-(2^3)+((500/2)/10)==8) && (13 > (7 + 1))) || (9 < 2)false
(Das entspricht natürlich dem oben gezeigten parse-tree)
Es gilt also für den Vorrang:
Potenz > Multiplikation/Division > Addition/Subtraktion > Vergleiche > logisches && > logisches || > Zuweisung
Damit wird ein Ausdruck wie
a = x <= y + z && x > z/2 sinnvoll ausgewertet als a = ((x <= (y+z)) && (x < (z/2)))
Eine Besonderheit sind noch
+ und - als Vorzeichen*-SymbolBeide haben Vorrang noch vor Multiplikation und Division.
Damit ändert sich die Bedeutung von Ausdrücken, wenn man juxtaposition anwendet:
1/2*π, 1/2π(1.5707963267948966, 0.15915494309189535)
Im Vergleich zum Potenzoperator ^ gilt (s. 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:
-2^2 # -(2^2)-4
x = 5
2x^2 # 2(x^2)50
2^-2 # 2^(-2)0.25
2^2x # 2^(2x)1024
f(...) hat Vorrang vor allen Operatorensin(x)^2 === (sin(x))^2 # nicht sin(x^2)true
Der Julia-Parser 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.
So haben z.B.
∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛den Vorrang 12 wie Multiplikation/Division (und sind wie diese linksassoziativ) und z.B.
⊕ ⊖ ⊞ ⊟ |++| ∪ ∨ ⊔ ± ∓ ∔ ∸ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ haben den Vorrang 11 wie Addition/Subtraktion.