624 lines
15 KiB
Plaintext
624 lines
15 KiB
Plaintext
---
|
||
engine: julia
|
||
---
|
||
|
||
```{julia}
|
||
#| error: false
|
||
#| echo: false
|
||
#| output: false
|
||
using InteractiveUtils
|
||
```
|
||
|
||
# Grundlagen der Syntax
|
||
|
||
## Namen von Variablen, Funktionen, Typen etc.
|
||
|
||
- Namen können Buchstaben, Ziffern, den Unterstrich `_` und das Ausrufezeichen `!` enthalten.
|
||
- Das erste Zeichen muss ein Buchstabe oder ein Unterstrich sein.
|
||
- Groß- und Kleinbuchstaben werden unterschieden: `Nmax` und `NMAX` sind verschiedene Variablen.
|
||
- Als Zeichensatz wird [Unicode](https://home.unicode.org/) verwendet. Damit stehen über 150 Schriften und zahlreiche Symbole zur Verfügung.
|
||
- Es gibt eine kurze [Liste reservierter Schlüsselwörter](https://docs.julialang.org/en/v1/base/base/#Keywords): `if, then, function, true, false,...`
|
||
|
||
:::{.callout-tip}
|
||
## Beispiel
|
||
|
||
zulässig: `i, x, Ω, x2, DieUnbekannteZahl, neuer_Wert, 🎷, Zähler, лічильник, einself!!!!,...`
|
||
|
||
unzulässig: `Uwe's_Funktion, 3achsen, A#B, $this_is_not_Perl, true,...`
|
||
:::
|
||
|
||
----
|
||
|
||
:::{.callout-note }
|
||
## Anmerkung
|
||
|
||
Neben den *reserved keywords* der Kernsprache sind zahlreiche weitere Funktionen und Objekte vordefiniert, wie z.B. die mathematischen Funktionen `sqrt(), log(), sin()`.
|
||
Diese Definitionen finden sich in dem Modul `Base`, welches Julia beim Start automatisch lädt.
|
||
Namen aus `Base` können umdefiniert werden, solange sie noch nicht verwendet wurden:
|
||
|
||
```{julia}
|
||
#| error: true
|
||
log = 3
|
||
1 + log
|
||
```
|
||
Jetzt ist natürlich der Logarithmus kaputt:
|
||
```{julia}
|
||
#| error: true
|
||
x = log(10)
|
||
```
|
||
(siehe auch <https://stackoverflow.com/questions/65902105/how-to-reset-any-function-in-julia-to-its-original-state>)
|
||
:::
|
||
|
||
## Anweisungen
|
||
|
||
- Im Normalfall enthält eine Zeile eine Anweisung.
|
||
- Wenn eine Anweisung am Zeilenende als unvollständig erkennbar ist durch
|
||
- offene Klammern
|
||
- Operationszeichen,
|
||
|
||
dann wird die nächste Zeile als Fortsetzung aufgefasst.
|
||
- Mehrere Anweisungen pro Zeile können durch Semikolon getrennt werden.
|
||
- Im interaktiven Betrieb (REPL oder Notebook) unterdrückt ein Semikolon nach der letzten Anweisung die Ausgabe des Ergebnisses dieser Anweisung.
|
||
|
||
:::{.callout-tip}
|
||
|
||
## Beispiel
|
||
|
||
Im interaktiven Betrieb wird der Wert der letzten Anweisung auch ohne explizites `print()` ausgegeben:
|
||
```{julia}
|
||
println("Hallo 🌍!")
|
||
x = sum([i^2 for i=1:10])
|
||
```
|
||
Das Semikolon unterdrückt das:
|
||
```{julia}
|
||
println("Hallo 🌍!")
|
||
x = sum([i^2 for i=1:10]);
|
||
```
|
||
:::
|
||
|
||
---------
|
||
|
||
|
||
:::{.callout-warning }
|
||
|
||
Bei mehrzeiligen Anweisungen muss die fortzusetzende Zeile mit einer offenen Operation oder Klammer enden:
|
||
|
||
```{julia}
|
||
x = sin(π/2) +
|
||
3 * cos(0)
|
||
```
|
||
|
||
Also geht das Folgende schief, aber leider **ohne eine Fehlermeldung**!
|
||
```{julia}
|
||
#| error: true
|
||
#| warning: true
|
||
x = sin(π/2)
|
||
+ 3 * cos(0)
|
||
println(x)
|
||
```
|
||
Hier wird das `+` in der zweiten Zeile als Präfix-Operator (Vorzeichen) interpretiert. Damit sind 1. und 2. Zeile jeweils für sich vollständige, korrekte Ausdrücke (auch wenn die 2. Zeile natürlich völlig nutzlos ist) und werden auch so abgearbeitet.
|
||
|
||
Moral: Wenn man längere Ausdrücke auf mehrere Zeilen aufteilen will, sollte man immer eine Klammer aufmachen. Dann ist egal, wo der Zeilenumbruch ist:
|
||
```{julia}
|
||
x = ( sin(π/2)
|
||
+ 3 * cos(0) )
|
||
println(x)
|
||
```
|
||
:::
|
||
|
||
|
||
## Kommentare
|
||
|
||
Julia kennt 2 Arten von Kommentaren im Programmtext:
|
||
|
||
```{julia}
|
||
# Einzeilige Kommentare beginnen mit einem Doppelkreuz.
|
||
|
||
x = 2 # alles vom '#' bis zum Zeilenende ist ein Kommentar und wird ignoriert. x = 3
|
||
```
|
||
|
||
```{julia}
|
||
#=
|
||
Ein- und mehrzeilige Kommentare können zwischen #= ... =# eingeschlossen werden.
|
||
Dabei sind verschachtelte Kommentare möglich.
|
||
#=
|
||
d.h., anders als in C/C++/Java endet der Kommentar nicht mit dem ersten
|
||
Kommentar-Endezeichen, sondern die #=...=# - Paare wirken wie Klammern.
|
||
=#
|
||
Der automatische 'syntax highlighter' weiss das leider noch nicht, wie die wechselnde
|
||
Graufärbung dieses Kommentars zeigt.
|
||
=#
|
||
|
||
|
||
x #= das ist ein seltener Variablenname! =# = 3
|
||
```
|
||
|
||
|
||
## Datentypen Teil I
|
||
|
||
- Julia ist eine [stark typisierte](https://de.wikipedia.org/wiki/Starke_Typisierung) Sprache. Alle Objekte haben einen Typ. Funktionen/Operationen erwarten Argumente mit dem richtigen Typ.
|
||
- Julia ist eine [dynamisch typisierte](https://de.wikipedia.org/wiki/Dynamische_Typisierung) Sprache. Variablen haben keinen Typ. Sie sind Namen, die durch Zuweisung `x = ...` an Objekte gebunden werden können.
|
||
- Wenn man vom „Typ einer Variablen“ spricht, meint man den Typ des Objektes, das der Variablen gerade zugewiesen ist.
|
||
- Funktionen/Operatoren können verschiedene *methods* für verschiedene Argumenttypen implementieren.
|
||
- Abhängig von den konkreten Argumenttypen wird dann bei Verwendung einer Funktion entschieden, welche Methode benutzt wird ([*dynamic dispatch*](https://en.wikipedia.org/wiki/Dynamic_dispatch)).
|
||
|
||
|
||
Einfache Basistypen sind z.B.:
|
||
|
||
```
|
||
Int64, Float64, String, Char, Bool
|
||
```
|
||
|
||
|
||
```{julia}
|
||
x = 2
|
||
x, typeof(x), sizeof(x)
|
||
```
|
||
|
||
```{julia}
|
||
x = 0.2
|
||
x, typeof(x), sizeof(x)
|
||
```
|
||
|
||
```{julia}
|
||
x = "Hallo!"
|
||
x, typeof(x), sizeof(x)
|
||
```
|
||
|
||
|
||
```{julia}
|
||
x = 'Ω'
|
||
x, typeof(x), sizeof(x)
|
||
```
|
||
|
||
```{julia}
|
||
x = 3 > π
|
||
x, typeof(x), sizeof(x)
|
||
```
|
||
|
||
- `sizeof()` liefert die Größe eines Objekts oder Typs in Bytes (1 Byte = 8 Bit)
|
||
- 64bit Ganzzahlen und 64bit Gleitkommazahlen entsprechen dem Befehlssatz moderner Prozessoren und sind daher die numerischen Standardtypen.
|
||
- Zeichen/*chars* `'A'` und Zeichenketten/*strings* `"A"` der Länge 1 sind verschiedene Objekte.
|
||
|
||
## Ablaufsteuerung
|
||
|
||
### `if`-Blöcke
|
||
|
||
- Ein `if`-Block *kann* **beliebig viele** `elseif`-Zweige und als letztes maximal **einen** `else`-Zweig enthalten.
|
||
- Der Block hat einen Wert, den Wert der letzten ausgeführten Anweisung.
|
||
|
||
```{julia}
|
||
x = 33
|
||
y = 44
|
||
z = 34
|
||
|
||
if x < y && z != x # elseif- und else-Blöcke sind optional
|
||
println("yes")
|
||
x += 10
|
||
elseif x < z # beliebig viele elseif-Blöcke
|
||
println(" x is smaller than z")
|
||
elseif x == z+1
|
||
println(" x is successor of z")
|
||
else # maximal ein else-Block
|
||
println("Alles falsch")
|
||
end # Wert des gesamten Blocks ist der Wert der
|
||
# letzten ausgeführten Auswertung
|
||
```
|
||
|
||
Kurze Blöcke kann man in eine Zeile schreiben:
|
||
```{julia}
|
||
if x > 10 println("x is larger than 10") end
|
||
```
|
||
|
||
Der Wert eines `if`-Blocks kann natürlich zugewiesen werden:
|
||
```{julia}
|
||
y = 33
|
||
z = if y > 10
|
||
println("y is larger than 10")
|
||
y += 1
|
||
end
|
||
z
|
||
```
|
||
|
||
### Auswahloperator (ternary operator) `test ? exp1 : exp2`
|
||
|
||
```{julia}
|
||
x = 20
|
||
y = 15
|
||
z = x < y ? x+1 : y+1
|
||
```
|
||
ist äquivalent zu
|
||
|
||
```{julia}
|
||
z = if x < y
|
||
x+1
|
||
else
|
||
y+1
|
||
end
|
||
```
|
||
|
||
## Vergleiche, Tests, Logische Operationen
|
||
|
||
### Arithmetische Vergleiche
|
||
|
||
- `==`
|
||
- `!=`, `≠`
|
||
- `>`
|
||
- `>=`, `≥`
|
||
- `<`
|
||
- `<=`, `≤`
|
||
|
||
Wie üblich, ist der Test auf Gleichheit `==` vom Zuweisungsoperator `=` zu unterscheiden. Vergleichen lässt sich so gut wie alles:
|
||
|
||
```{julia}
|
||
"Aachen" < "Leipzig", 10 ≤ 10.01, [3,4,5] < [3,6,2]
|
||
```
|
||
|
||
Nun ja, fast alles:
|
||
|
||
```{julia}
|
||
3 < "vier"
|
||
```
|
||
|
||
Die Fehlermeldung zeigt ein paar Grundprinzipien von Julia:
|
||
|
||
- Operatoren sind auch nur Funktionen: `x < y` wird zum Funktionsaufruf `isless(x, y)`.
|
||
- Funktionen (und damit Operatoren) können verschiedene *methods* für verschiedene Argumenttypen implementieren.
|
||
- Abhängig von den konkreten Argumenttypen wird beim Aufruf der Funktion entschieden, welche Methode benutzt wird ([*dynamic dispatch*](https://en.wikipedia.org/wiki/Dynamic_dispatch)).
|
||
|
||
Man kann sich alle Methoden zu einer Funktion anzeigen lassen. Das gibt einen Einblick in das komplexe Typssystem von Julia:
|
||
|
||
```{julia}
|
||
methods(<)
|
||
```
|
||
|
||
Zuletzt noch: Vergleiche können gekettet werden.
|
||
|
||
```{julia}
|
||
10 < x ≤ 100 # das ist äquivalent zu
|
||
# 10 < x && x ≤ 100
|
||
|
||
```
|
||
|
||
|
||
### Tests
|
||
Einge Funktionen vom Typ `f(c::Char) -> Bool`
|
||
|
||
```{julia}
|
||
isnumeric('a'), isnumeric('7'), isletter('a')
|
||
```
|
||
|
||
|
||
und vom Typ `f(s1::String, s2::String) -> Bool`
|
||
|
||
```{julia}
|
||
contains("Lampenschirm", "pensch"), startswith("Lampenschirm", "Lamb"), endswith("Lampenschirm", "rm")
|
||
```
|
||
|
||
- Die Funktion `in(item, collection) -> Bool` testet, ob `item` in `collection` ist.
|
||
- Sie hat auch das Alias ` ∈(item, collection)` und
|
||
- sowohl `in` als auch `∈` können auch als Infix-Operatoren geschrieben werden.
|
||
|
||
```{julia}
|
||
x = 3
|
||
x in [1, 2, 3, 4, 5]
|
||
```
|
||
```{julia}
|
||
x ∈ [1, 2, 33, 4, 5]
|
||
```
|
||
|
||
### Logische Operationen: `&&`, `||`, `!`
|
||
|
||
|
||
```{julia}
|
||
3 < 4 && !(2 > 8) && !contains("aaa", "b")
|
||
```
|
||
|
||
#### Bedingte Auswertung (_short circuit evaluation_)
|
||
|
||
- in `a && b` wird `b` nur ausgewertet, wenn `a == true`
|
||
- in `a || b` wird `b` nur ausgewertet, wenn `a == false`
|
||
|
||
(i) Damit kann `if test statement end` auch als `test && statement` geschrieben werden.
|
||
|
||
(ii) Damit kann `if !test statement end` als `test || statement` geschrieben werden.
|
||
|
||
Als Beispiel^[aus der [Julia-Dokumentation](https://docs.julialang.org/en/v1/manual/control-flow/#Short-Circuit-Evaluation)] hier eine Implementierung der Fakultätsfunktion *(factorial)*:
|
||
```{julia}
|
||
function fact(n::Int)
|
||
n >= 0 || error("n must be non-negative")
|
||
n == 0 && return 1
|
||
n * fact(n-1)
|
||
end
|
||
|
||
fact(5)
|
||
```
|
||
|
||
|
||
|
||
Natürlich kann man alle diese Tests auch Variablen vom Typ `Bool` zuordnen und
|
||
diese Variablen können als Tests in `if`- und `while`-Blöcken verwendet werden:
|
||
|
||
```{julia}
|
||
x = 3 < 4
|
||
y = 5 ∈ [1, 2, 5, 7]
|
||
z = x && y
|
||
if z # äquivalent zu: if 3 < 4 && 5 in [1,2,5,7]
|
||
println("Stimmt alles!")
|
||
end
|
||
```
|
||
|
||
- In Julia müssen alle Tests in einem logischen Ausdruck vom Typ `Bool` sein.
|
||
- Es gibt keine implizite Konvertierung à la *"0 is false and 1 (or anything != 0) is true"*
|
||
- Wenn `x` ein numerischer Typ ist, dann muss daher das C-Idiom `if(x)` als `if x != 0` geschrieben werden.
|
||
- Es gibt eine Ausnahme zur Unterstützung der _short circuit evaluation_:
|
||
- bei den Konstrukten `a && b && c...` bzw `a || b || c...` muss der letzte Teilausdruck nicht vom Typ `Bool` sein, wenn diese Konstrukte nicht als Tests in `if` oder `while` verwendet werden:
|
||
|
||
|
||
```{julia}
|
||
z = 3 < 4 && 10 < 5 && sqrt(3^3)
|
||
z, typeof(z)
|
||
```
|
||
|
||
```{julia}
|
||
z = 3 < 4 && 10 < 50 && sqrt(3^3)
|
||
z, typeof(z)
|
||
```
|
||
|
||
## Schleifen *(loops)*
|
||
|
||
### Die `while` ("solange")-Schleife
|
||
|
||
|
||
Syntax:
|
||
```
|
||
while *condition*
|
||
*loop body*
|
||
end
|
||
```
|
||
Eine Reihe von Anweisungen (der Schleifenkörper) wird immer wieder abgearbeitet, solange eine Bedingung erfüllt ist.
|
||
|
||
|
||
```{julia}
|
||
i = 1 # typischerweise braucht der Test der
|
||
# while-Schleife eine Vorbereitung ...
|
||
while i < 10
|
||
println(i)
|
||
i += 2 # ... und ein update
|
||
end
|
||
```
|
||
|
||
Der Körper einer `while`- und `for`-Schleife kann die Anweisungen `break` und `continue` enthalten. `break` stoppt die Schleife, `continue` überspringt den Rest des Schleifenkörpers und beginnt sofort mit dem nächsten Schleifendurchlauf.
|
||
```{julia}
|
||
i = 0
|
||
|
||
while i<10
|
||
i += 1
|
||
|
||
if i == 3
|
||
continue # beginne sofort nächsten Durchlauf,
|
||
end # überspringe Rest des Schleifenkörpers
|
||
|
||
println("i = $i")
|
||
|
||
if i ≥ 5
|
||
break # breche Schleife ab
|
||
end
|
||
end
|
||
|
||
println("Fertig!")
|
||
```
|
||
|
||
Mit `break` kann man auch Endlosschleifen verlassen:
|
||
|
||
```{julia}
|
||
i = 1
|
||
|
||
while true
|
||
println(2^i)
|
||
i += 1
|
||
if i > 8 break end
|
||
end
|
||
```
|
||
|
||
### `for`-Schleifen
|
||
|
||
Syntax:
|
||
|
||
```
|
||
for *var* in *iterable container*
|
||
*loop body*
|
||
end
|
||
```
|
||
|
||
Der Schleifenkörper wird für alle Items aus einem Container durchlaufen.
|
||
|
||
Statt `in` kann immer auch $\in$ verwendet werden. Im Kopf einer `for`-Schleife kann auch `=` verwendet werden.
|
||
|
||
```{julia}
|
||
for i ∈ ["Mutter", "Vater", "Tochter"]
|
||
println(i)
|
||
end
|
||
```
|
||
|
||
|
||
Oft benötigt man einen numerischen Schleifenzähler. Dafür gibt es das *range*-Konstrukt. Die einfachsten Formen sind
|
||
`Start:Ende` und `Start:Schrittweite:Ende`.
|
||
|
||
```{julia}
|
||
endwert = 5
|
||
|
||
for i ∈ 1:endwert
|
||
println(i^2)
|
||
end
|
||
```
|
||
|
||
```{julia}
|
||
for i = 1:5.5 print(" $i") end
|
||
```
|
||
|
||
|
||
```{julia}
|
||
for i = 1:2:14 print(" $i") end
|
||
```
|
||
|
||
```{julia}
|
||
for k = 14 : -2.5 : 1 print(" $k") end
|
||
```
|
||
|
||
|
||
|
||
|
||
#### Geschachtelte Schleifen _(nested loops)_
|
||
|
||
Ein `break` beendet die innerste Schleife.
|
||
|
||
```{julia}
|
||
for i = 1:3
|
||
for j = 1:3
|
||
println( (i,j) )
|
||
if j == 2
|
||
break
|
||
end
|
||
end
|
||
end
|
||
```
|
||
|
||
Man kann *nested loops* auch in einer `for`-Anweisung zusammenfassen. Dann beendet ein `break` die Gesamtschleife.
|
||
|
||
```{julia}
|
||
for i = 1:3, j=1:3 # im Prinzip dasselbe wie oben, aber:
|
||
println( (i,j) )
|
||
if j == 2
|
||
break # break bricht hier die Gesamtschleife ab
|
||
end
|
||
end
|
||
```
|
||
|
||
|
||
:::{.callout-important .titlenormalxx}
|
||
## **Wichtig:** Die Semantik ist völlig anders, als bei C-artigen `for`-Schleifen!
|
||
|
||
**Bei jedem Schleifendurchlauf wird die Laufvariable neu mit dem nächsten Element aus dem Container initialisiert.**
|
||
|
||
|
||
```{julia}
|
||
for i = 1:5
|
||
print(i," ... ")
|
||
i += 2
|
||
println(i)
|
||
end
|
||
```
|
||
|
||
-------
|
||
|
||
Die C-Semantik von `for(i=1; i<5; i++)` entspricht der `while`-Schleife:
|
||
```
|
||
i = 1
|
||
while i<5
|
||
*loop body* # hier kann auch wirksam an i rumgepfuscht werden
|
||
i += 1
|
||
end
|
||
```
|
||
:::
|
||
|
||
## Unicode
|
||
|
||
Julia verwendet Unicode als Zeichensatz. Damit können für Variablen, Funktionen etc auch Bezeichner aus nicht-lateinischen Schriften (zB Kyrillisch, Koreanisch, Sanskrit, Runen,
|
||
Emoji,...) verwendet werden. Die Frage, wie man solche Zeichen in seinem Editor eingeben kann und ob der verwendete Bildschirm-Font sie darstellen kann, ist nicht Julias Problem.
|
||
|
||
- Einige Unicode-Zeichen, z.B. `≤, ≠, ≥, π, ∈, √`, können anstelle von `<=, !=, >=, pi, in, sqrt` verwendet werden.
|
||
|
||
- über 3000 Unicode-Zeichen können in Julia in einer LaTeX-ähnlichen Weise mit der Tab-Vervollständigung eingegeben werden.
|
||
- `\alpha<TAB>` wird zu `α`,
|
||
- `\euler<TAB>` wird zu `ℯ` (Eulersche Zahl `exp(1)`, [spezielles Schreibschrift-e, `U+0212F`](https://www.htmlsymbol.com/unicode-code/212f.html))
|
||
- `\le<TAB>` wird zu `≤`,
|
||
- `\in<TAB>` wird zu `∈`,
|
||
- `\:rainbow:<TAB>` wird zu `🌈`
|
||
[Hier geht es zur Liste.](https://docs.julialang.org/en/v1/manual/unicode-input/)
|
||
|
||
## Eigenheiten und Stolperfallen der Syntax
|
||
|
||
- Man kann den Multiplikationsoperator `*` nach einer numerischen Konstanten weglassen, wenn eine Variable, Funktion oder öffnende Klammer folgt.
|
||
```
|
||
z = 3.4x + 2(x+y) + xy
|
||
```
|
||
|
||
ist daher korrektes Julia. Beachte allerdings, dass der Term `xy` als eine Variable mit dem Namen xy interpretiert wird __und nicht__ als Produkt von x und y!
|
||
|
||
- Diese Regel hat ein paar Tücken:
|
||
|
||
Das funktioniert wie erwartet:
|
||
```{julia}
|
||
e = 7
|
||
3e
|
||
```
|
||
|
||
Hier wird die Eingabe als Gleitkommazahl interpretiert -- und `3E+2` oder `3f+2` (Float32) ebenso.
|
||
|
||
```{julia}
|
||
3e+2
|
||
```
|
||
|
||
Ein Leerzeichen schafft Eindeutigkeit:
|
||
|
||
```{julia}
|
||
3e + 2
|
||
```
|
||
|
||
Das funktioniert:
|
||
|
||
```{julia}
|
||
x = 4
|
||
3x + 3
|
||
```
|
||
|
||
...und das nicht. `0x`, `0o`, `0b` wird als Anfang einer Hexadezimal-, Oktal- bzw. Binärkonstanten interpretiert.
|
||
|
||
```{julia}
|
||
3y + 0x
|
||
```
|
||
|
||
|
||
- Es gibt noch ein paar andere Fälle, bei denen die sehr kulante Syntax zu Überraschungen führt.
|
||
|
||
|
||
```{julia}
|
||
Wichtig = 21
|
||
Wichtig! = 42 # Bezeichner können auch ein ! enthalten
|
||
(Wichtig, Wichtig!)
|
||
```
|
||
|
||
|
||
|
||
```{julia}
|
||
Wichtig!=88
|
||
```
|
||
|
||
|
||
Julia interpretiert das als Vergleich `Wichtig != 88`.
|
||
|
||
Leerzeichen helfen:
|
||
|
||
|
||
```{julia}
|
||
Wichtig! = 88
|
||
Wichtig!
|
||
```
|
||
|
||
- Operatoren der Form `.*`, `.+`,... haben in Julia eine spezielle Bedeutung (*broadcasting*, d.h., vektorisierte Operationen).
|
||
|
||
```{julia}
|
||
1.+2.
|
||
```
|
||
|
||
Wieder gilt: Leerzeichen schaffen Klarheit!
|
||
|
||
```{julia}
|
||
1. + 2.
|
||
```
|
||
|
||
|
||
|
||
|