Files
JuliaKurs23/chapters/syntax.qmd

622 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
engine: julia
julia:
exeflags: ["--color=yes"]
---
```{julia}
#| error: false
#| echo: false
#| output: false
using InteractiveUtils
```
# Fundamentals of Syntax
## Names of Variables, Functions, Types, etc.
- Names may contain letters, digits, underscores `_`, and exclamation marks `!`.
- The first character must be a letter or an underscore.
- Case is significant: `Nmax` and `NMAX` are different variables.
- The character set used is [Unicode](https://home.unicode.org/), which provides access to over 150 scripts and numerous symbols.
- There is a short [list of reserved keywords](https://docs.julialang.org/en/v1/base/base/#Keywords): `if, then, function, true, false,...`
:::{.callout-tip}
## Example
Permissible: `i, x, Ω, x2, DieUnbekannteZahl, neuer_Wert, 🎷, Zähler, лічильник, einself!!!!,...`
Impermissible: `Uwe's_Funktion, 3achsen, A#B, $this_is_not_Perl, true,...`
:::
----
:::{.callout-note }
## Note:
In addition to the *reserved keywords* of the core language, numerous additional functions and objects are predefined, such as the mathematical functions `sqrt(), log(), sin()`.
These definitions are found in the module `Base`, which Julia loads automatically at startup.
Names from `Base` can be redefined as long as they have not yet been used:
```{julia}
#| error: true
log = 3
1 + log
```
Now, of course, the logarithm is broken:
```{julia}
#| error: true
x = log(10)
```
(see also <https://stackoverflow.com/questions/65902105/how-to-reset-any-function-in-julia-to-its-original-state>)
:::
## Statements
- In normal circumstances, each line contains one statement.
- If a statement is recognizable as incomplete at the end of a line through
- open parentheses
- operators,
then the next line is regarded as a continuation.
- Multiple statements per line can be separated by semicolons.
- In interactive mode (REPL or notebook), a semicolon after the last statement suppresses the output of the result of that statement.
:::{.callout-tip}
## Example:
In interactive mode, the value of the last statement is also displayed without explicit `print()`:
```{julia}
println("Hallo 🌍!")
x = sum([i^2 for i=1:10])
```
The semicolon suppresses this:
```{julia}
println("Hallo 🌍!")
x = sum([i^2 for i=1:10]);
```
:::
---------
:::{.callout-warning }
For multi-line statements, the line to be continued must end with an open operator or parenthesis.
```{julia}
x = sin(π/2) +
3 * cos(0)
```
Therefore, the following goes wrong, but—unfortunately—**without an error message**!
```{julia}
#| error: true
#| warning: true
x = sin(π/2)
+ 3 * cos(0)
println(x)
```
Here, the `+` in the second line is interpreted as a prefix operator (sign). Thus, lines 1 and 2 are each complete, correct expressions on their own (even though line 2 is of course completely useless) and are processed as such.
**Moral:** If you want to split longer expressions across multiple lines, you should always open a parenthesis. Then it doesn't matter where the line break occurs.
```{julia}
x = ( sin(π/2)
+ 3 * cos(0) )
println(x)
```
:::
## Comments
Julia knows two types of comments in program text:
```{julia}
# Single-line comments begin with a hash symbol.
x = 2 # everything from '#' to the end of the line is a comment and is ignored. x = 3
```
```{julia}
#=
Single and multi-line comments can be enclosed between `#= ... =#`. Nested comments are possible.
`#=`
i.e., unlike in C/C++/Java, the comment does not end with the first comment-end character, but the `#=...=#` pairs act like parentheses.
`=#`
The automatic 'syntax highlighter' does not yet know this, as the alternating
gray shading of this comment shows.
=#
x #= das ist ein seltener Variablenname! =# = 3
```
## Data Types Part I
- Julia is a [strongly typed](https://de.wikipedia.org/wiki/Starke_Typisierung) language. All objects have a type. Functions and operations expect arguments of the correct type.
- Julia is a [dynamically typed](https://de.wikipedia.org/wiki/Dynamische_Typisierung) language. Variables have no type. They are names that can be bound to objects via assignment `x = ...`.
- When speaking of the "type of a variable", one means the type of the object currently assigned to the variable.
- Functions and operators can implement different *methods* for different argument types.
- Depending on the concrete argument types, it is decided at function usage which method is used ([*dynamic dispatch*](https://en.wikipedia.org/wiki/Dynamic_dispatch)).
Simple basic types are, for example:
```
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()` returns the size of an object or type in bytes (1 byte = 8 bits)
- 64-bit integers and 64-bit floating-point numbers correspond to the instruction set of modern processors and are therefore the standard numeric types.
- Characters/*chars* `'A'` and strings/*strings* `"A"` of length 1 are different objects.
## Control Flow
### `if` Blocks
- An `if` block can contain any number of `elseif` branches and, at the end, at most one `else` branch.
- The block has a value: the value of the last executed statement.
```{julia}
x = 33
y = 44
z = 34
if x < y && z != x # elseif- and else-branches are optional
println("yes")
x += 10
elseif x < z # any number of elseif branches
println(" x is smaller than z")
elseif x == z+1
println(" x is successor of z")
else # at most one else block
println("Alles falsch")
end # value of the entire block is the value of the
# last evaluated statement
```
Short blocks can be written on one line:
```{julia}
if x > 10 println("x is larger than 10") end
```
The value of an `if` block can of course be assigned:
```{julia}
y = 33
z = if y > 10
println("y is larger than 10")
y += 1
end
z
```
### Conditional Operator (ternary operator) `test ? exp1 : exp2`
```{julia}
x = 20
y = 15
z = x < y ? x+1 : y+1
```
is equivalent to
```{julia}
z = if x < y
x+1
else
y+1
end
```
## Comparisons, Tests, Logical Operations
### Arithmetic Comparisons
- `==`
- `!=`, `≠`
- `>`
- `>=`, `≥`
- `<`
- `<=`, `≤`
As usual, the equality test `==` must be distinguished from the assignment operator `=`. Almost anything can be compared.
```{julia}
"Aachen" < "Leipzig", 10 ≤ 10.01, [3,4,5] < [3,6,2]
```
Well, almost anything:
```{julia}
3 < "vier"
```
The error message shows a few fundamental principles of Julia:
- Operators are also just functions: `x < y` becomes the function call `isless(x, y)`.
- Functions (and thus operators) can implement different *methods* for different argument types.
- Depending on the concrete argument types, it is decided at function call which method is used ([*dynamic dispatch*](https://en.wikipedia.org/wiki/Dynamic_dispatch)).
One can display all methods for a function. This provides insight into Julia's complex type system:
```{julia}
methods(<)
```
Finally: comparisons can be chained.
```{julia}
10 < x ≤ 100 # this is equivalent to
# 10 < x && x ≤ 100
```
### Tests
Some functions of type `f(c::Char) -> Bool`
```{julia}
isnumeric('a'), isnumeric('7'), isletter('a')
```
and of type `f(s1::String, s2::String) -> Bool`
```{julia}
contains("Lampenschirm", "pensch"), startswith("Lampenschirm", "Lamb"), endswith("Lampenschirm", "rm")
```
- The function `in(item, collection) -> Bool` tests whether `item` is in `collection`.
- It also has the alias ` ∈(item, collection)` and
- both `in` and `∈` can also be written as infix operators.
```{julia}
x = 3
x in [1, 2, 3, 4, 5]
```
```{julia}
x ∈ [1, 2, 33, 4, 5]
```
### Logical Operations: `&&`, `||`, `!`
```{julia}
3 < 4 && !(2 > 8) && !contains("aaa", "b")
```
#### Conditional Evaluation (_short circuit evaluation_)
- in `a && b` `b` is only evaluated if `a == true`
- in `a || b` `b` is only evaluated if `a == false`
(i) Thus, `if test statement end` can also be written as `test && statement`.
(ii) Thus, `if !test statement end` can be written as `test || statement`.
As an example^[from the [Julia documentation](https://docs.julialang.org/en/v1/manual/control-flow/#Short-Circuit-Evaluation)] here is an implementation of the factorial function:
```{julia}
function fact(n::Int)
n >= 0 || error("n must be non-negative")
n == 0 && return 1
n * fact(n-1)
end
fact(5)
```
Of course, all these tests can also be assigned to variables of type `Bool` and
these variables can be used as tests in `if` and `while` blocks:
```{julia}
x = 3 < 4
y = 5 ∈ [1, 2, 5, 7]
z = x && y
if z # equivalent to: if 3 < 4 && 5 in [1,2,5,7]
println("Stimmt alles!")
end
```
- In Julia, all tests in a logical expression must be of type `Bool`.
- There is no implicit conversion such as *"0 is false and 1 (or anything != 0) is true"*
- If `x` is a numeric type, then the C idiom `if(x)` must be written as `if x != 0`.
- There is an exception to support the _short circuit evaluation_:
- in the constructs `a && b && c...` or `a || b || c...` the last subexpression does not need to be of type `Bool` if these constructs are not used as tests in `if` or `while`:
```{julia}
z = 3 < 4 && 10 < 5 && sqrt(3^3)
z, typeof(z)
```
```{julia}
z = 3 < 4 && 10 < 50 && sqrt(3^3)
z, typeof(z)
```
## Loops *(loops)*
### The `while` ("while") loop
Syntax:
```
while *condition*
*loop body*
end
```
A series of statements (the loop body) is repeatedly executed as long as a condition is satisfied.
```{julia}
i = 1 # typically the test of the
# while loop needs preparation ...
while i < 10
println(i)
i += 2 # ... and an update
end
```
The body of a `while` and `for` loop can contain the statements `break` and `continue`. `break` stops the loop, `continue` skips the rest of the loop body and immediately starts the next loop iteration.
```{julia}
i = 0
while i<10
i += 1
if i == 3
continue # start next iteration immediately,
end # skip rest of loop body
println("i = $i")
if i ≥ 5
break # break loop
end
end
println("Fertig!")
```
With `break` one can also exit infinite loops:
```{julia}
i = 1
while true
println(2^i)
i += 1
if i > 8 break end
end
```
### `for` Loops
Syntax:
```
for *var* in *iterable container*
*loop body*
end
```
The loop body is executed for all items from a container.
Instead of `in`, $\in$ can always be used. In the header of a `for` loop, `=` can also be used.
```{julia}
for i ∈ ["Mutter", "Vater", "Tochter"]
println(i)
end
```
Often a numerical loop counter is needed. For this purpose, there is the *range* construct. The simplest forms are
`Start:End` and `Start:Step:End`.
```{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
```
#### Nested Loops _(nested loops)_
A `break` ends the innermost loop.
```{julia}
for i = 1:3
for j = 1:3
println( (i,j) )
if j == 2
break
end
end
end
```
*Nested loops* can also be combined in a single `for` statement. Then a `break` ends the entire loop.
```{julia}
for i = 1:3, j=1:3 # essentially the same as above, but:
println( (i,j) )
if j == 2
break # break ends the entire loop here
end
end
```
:::{.callout-important .titlenormalxx}
## **Important:** The semantics are completely different from C-style `for` loops!
**In each loop iteration, the loop variable is re-initialized with the next element from the container.**
```{julia}
for i = 1:5
print(i," ... ")
i += 2
println(i)
end
```
-------
The C semantics of `for(i=1; i<5; i++)` corresponds to the `while` loop:
```{.julia}
i = 1
while i<5
*loop body* # here one can also mess with i effectively
i += 1
end
```
:::
## Unicode
Julia uses Unicode as its character set. This allows identifiers from non-Latin scripts (e.g., Cyrillic, Korean, Sanskrit, runes,
emojis,...) to be used for variables, functions, etc. The question of how one can enter such characters in their editor and whether the used screen font can display them is not Julia's problem.
- Some Unicode characters, e.g., `≤, ≠, ≥, π, ∈, √`, can be used instead of `<=, !=, >=, pi, in, sqrt`.
- Over 3000 Unicode characters can be entered in Julia in a LaTeX-like manner using tab completion.
- `\alpha<TAB>` becomes `α`,
- `\euler<TAB>` becomes `` (Euler's number `exp(1)`, [special script e, `U+0212F`](https://www.htmlsymbol.com/unicode-code/212f.html))
- `\le<TAB>` becomes `≤`,
- `\in<TAB>` becomes `∈`,
- `\:rainbow:<TAB>` becomes `🌈`
[Here is the list.](https://docs.julialang.org/en/v1/manual/unicode-input/)
## Idiosyncrasies and Pitfalls of Syntax
- After a numeric constant, the multiplication operator `*` can be omitted when a variable, function, or opening parenthesis follows.
```
z = 3.4x + 2(x+y) + xy
```
is therefore valid Julia. Note, however, that the term `xy` is interpreted as a single variable named xy __and not__ as the product of x and y!
- This rule has a few pitfalls:
This works as expected:
```{julia}
e = 7
3e
```
Here, the input is interpreted as a floating-point number -- and `3E+2` or `3f+2` (Float32) as well.
```{julia}
3e+2
```
A space creates clarity:
```{julia}
3e + 2
```
This works:
```{julia}
x = 4
3x + 3
```
...and this does not. `0x`, `0o`, `0b` are interpreted as the beginning of a hexadecimal, octal, or binary constant.
```{julia}
3y + 0x
```
- There are a few other cases where the very permissive syntax leads to surprises.
```{julia}
Wichtig = 21
Wichtig! = 42 # identifiers can also contain !
(Wichtig, Wichtig!)
```
```{julia}
Wichtig!=88
```
Julia interprets this as the comparison `Wichtig != 88`.
Spaces help:
```{julia}
Wichtig! = 88
Wichtig!
```
- Operators of the form `.*`, `.+`,... have a special meaning in Julia (*broadcasting*, i.e., vectorized operations).
```{julia}
1.+2.
```
Again, spaces create clarity!
```{julia}
1. + 2.
```