# 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 ) ::: ## 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` wird zu `α`, - `\euler` wird zu `ℯ` (Eulersche Zahl `exp(1)`, [spezielles Schreibschrift-e, `U+0212F`](https://www.htmlsymbol.com/unicode-code/212f.html)) - `\le` wird zu `≤`, - `\in` wird zu `∈`, - `\:rainbow:` 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. ```