622 lines
14 KiB
Plaintext
622 lines
14 KiB
Plaintext
---
|
||
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.
|
||
```
|
||
|