function hyp(x,y)
sqrt(x^2+y^2)
end
hyp (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)
end
hyp (generic function with 1 method)
hyp(x, y) = sqrt(x^2 + y^2)
hyp (generic function with 1 method)
-> sqrt(x^2 + y^2) (x, y)
#1 (generic function with 1 method)
return
return
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)
end
und ohne das zweite explizite return
in der letzten Zeile:
function xsinrecipx(x)
if x == 0
return 0.0
end
* sin(1/x)
x end
sind 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
= fn(2) a
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
= x^2
z += y^2
z sqrt(z)
end
Anonyme FUnktionen kann man der Anonymität entreisen, indem man ihnen einen Namen zuweist.
= (x,y) -> sqrt(x^2 + y^2) hyp
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.= [1, 2, 3]
V
= fill!(V, 17)
W # '===' ist Test auf Identität
@show V W V===W; # V und W benennen dasselbe Objekt
V = [17, 17, 17]
W = [17, 17, 17]
V === W = true
function fill_first!(V, x)
1] = x
V[return V
end
= fill_first!(V, 42)
U
@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
= sqrt
f2 f2(2)
1.4142135623730951
# sehr naive numerische Integration
function Riemann_integrate(f, a, b; NInter=1000)
= (b-a)/NInter
delta = 0
s for i in 0:NInter-1
+= delta * f(a + delta/2 + i * delta)
s end
return s
end
Riemann_integrate(sin, 0, π)
2.0000008224672694
return
t werden.function generate_add_func(x)
function addx(y)
return x+y
end
return addx
end
generate_add_func (generic function with 1 method)
= generate_add_func(4) h
(::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 + y
generate_add_func (generic function with 1 method)
|>
\circ + Tab
) geschrieben werden\[(f\circ g)(x) = f(g(x))\]
∘ + )(9, 16) (sqrt
5.0
= cos ∘ sin ∘ (x->2x)
f 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 |> sqrt
5.0
1:10 |> sum |> sqrt
7.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
f(x,y)
Körper von end
Am 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 end
2.6666659999999993
Der Sinn besteht natürlich in der Anwendung mit komplexeren Funktionen, wie diesem aus zwei Teilstücken zusammengesetzten Integranden:
= Riemann_integrate(0, π) do x
r = sin(x)
z1 = log(1+x)
z2 if x > 1
return z1^2
else
return 1/z2^2
end
end
1578.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
::Float64
a0::Float64
a1::Float64
a2end
= Poly2Grad(2,5,1)
p1 = Poly2Grad(3,1,-0.4) p2
Poly2Grad(3.0, 1.0, -0.4)
Die folgende Methode macht diese Struktur callable
.
function (p::Poly2Grad)(x)
* x^2 + p.a1 * x + p.a0
p.a2 end
Jetzt 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 ⊙ y x
kann auch geschrieben werden als
⊙= y x
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
for i in eachindex(x)
+= y[i]
x[i] end
oder durch die dazu semantisch äquivalente broadcast-Form (s. Kapitel 12.7):
.= x .+ y x
Zu berechnende Ausdrücke
-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2
false
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)/2
20.0
200/2*5 # wird von links ausgewertet als (200/2)*5
500.0
Zuweisungen wie =
, +=
, *=
,… sind gleichrangig und rechtsassoziativ.
= 1
x = 10
y
# wird von rechts ausgewertet: x += (y += (z = (a = 20)))
+= y += z = a = 20
x
@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 (:/, :+=, :(=), :^)
= Base.operator_associativity(i)
a println("Operation $i is $(a)-assoziative")
end
Operation / 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 (:+, :-, :*, :/, :^, :(=))
= Base.operator_precedence(i)
p println("Vorrang von $i = $p")
end
Vorrang 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)
= 3 < 4
x x
true
= 3) < 4 # Klammern schlagen natürlich jeden Vorrang
(y y
3
Nochmal zum Beispiel vom Anfang von Kapitel 10.10:
-2^3+500/2/10==8 && 13 > 7 + 1 || 9 < 2
false
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
= x <= y + z && x > z/2 a
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
= 5
x 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.