4  Fundamentals of Syntax

4.1 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, which covers over 150 scripts and numerous symbols.
  • There is a short list of reserved keywords: if, then, function, true, false,...
TipExample

Permissible: i, x, Ω, x2, TheUnknownNumber, new_Value, 🎷, Counter_2, лічильник, yes!!!!,...

Impermissible: Karen's_Funktion, 3achsen, A#B, $this_is_not_Perl, true,...


NoteNote:

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 Base module, which Julia loads automatically on startup. Names from Base can be redefined as long as they have not yet been used:

log = 3
1 + log
4

Now, of course, log() no longer works:

x = log(10)
MethodError: objects of type Int64 are not callable
The object of type `Int64` exists, but no method is defined for this combination of argument types when trying to treat it as a callable object.
Maybe you forgot to use an operator such as *, ^, %, / etc. ?
Stacktrace:
 [1] top-level scope
   @ ~/Julia/Book26/JuliaBook/chapters/syntax.qmd:49

(see also https://stackoverflow.com/questions/65902105/how-to-reset-any-function-in-julia-to-its-original-state)

4.2 Statements

  • Usually, 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.
TipExample:

In interactive mode, the value of the last statement is also displayed without explicit print():

println("Hallo 🌍!")
x = sum([i^2 for  i=1:10])
Hallo 🌍!
385

The semicolon suppresses this:

println("Hallo 🌍!")
x = sum([i^2 for  i=1:10]);
Hallo 🌍!

Warning

For multi-line statements, a continued line should end with an open operator or parenthesis.

x = sin(π/2) +
   3 * cos(0)
4.0

Therefore, the following fails, but—unfortunately—without an error message!

x = sin(π/2)
  + 3 * cos(0)
println(x)
1.0

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.

x = ( sin(π/2) 
   + 3 * cos(0) )
println(x)
4.0

4.3 Comments

Julia knows two types of comments in program text:

# 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
2
#= 
Single and multi-line comments can be enclosed in `#= ...  =#`. 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' doesn't support this yet, as the alternating 
   gray shading of this comment shows.     
=#


x #= this is a really rare name for a variable! =#  = 3
3

4.4 Data Types Part I

  • Julia is a strongly typed language. All objects have a type. Functions and operations expect arguments of the correct type.
  • Julia is a dynamically typed 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 call time which method is selected (dynamic dispatch).

Simple basic types are, for example:

Int64, Float64, String, Char, Bool
x = 2
x, typeof(x), sizeof(x)
(2, Int64, 8)
x = 0.2
x, typeof(x), sizeof(x)
(0.2, Float64, 8)
x = "Hello!"
x, typeof(x), sizeof(x)
("Hello!", String, 6)
x = 'Ω'
x, typeof(x), sizeof(x)
('Ω', Char, 4)
x = 3 > π
x, typeof(x), sizeof(x)
(false, Bool, 1)
  • 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.
  • Character literals like 'A' and single-character strings like "A" are different objects.

4.5 Control Flow

4.5.1 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.
x = 33
y = 44
z = 34

if x < y && z != x                     # elseif or 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("All wrong")
end                                    # value of the entire block is the value of the 
                                       # last evaluated statement
yes
43

Short blocks can be written on one line:

if x > 10 println("x is larger than 10") end
x is larger than 10

The value of an if block can be assigned:

y = 33
z = if y > 10 
       println("y is larger than 10") 
       y += 1 
     end
z
y is larger than 10
34

4.5.2 Conditional Operator (ternary operator) test ? exp1 : exp2

x = 20
y = 15
z = x < y ? x+1 : y+1
16

is equivalent to

z = if x < y
        x+1
    else
        y+1
    end
16

4.6 Comparisons, Tests, Logical Operations

4.6.1 Arithmetic Comparisons

  • ==
  • !=,
  • >
  • >=,
  • <
  • <=,

As usual, the equality test == must be distinguished from the assignment operator =. Almost anything can be compared.

"Aachen" < "Leipzig", 10  10.01, [3,4,5] < [3,6,2]
(true, true, true)

Well, almost anything:

3 < "four"
MethodError: no method matching isless(::Int64, ::String)
The function `isless` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  isless(::Missing, ::Any)
   @ Base missing.jl:87
  isless(::Integer, ::Base.CoreLogging.LogLevel)
   @ Base logging/logging.jl:133
  isless(::Real, ::AbstractFloat)
   @ Base operators.jl:223
  ...

Stacktrace:
 [1] <(x::Int64, y::String)
   @ Base ./operators.jl:399
 [2] top-level scope
   @ ~/Julia/Book26/JuliaBook/chapters/syntax.qmd:261

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).

One can display all methods for a function. This provides insight into Julia’s complex type system:

methods(<)
# 74 methods for generic function < from Base:

Finally: comparisons can be chained.

10 < x  100  # this is equivalent to
              #   10 < x && x ≤ 100
true

4.6.2 Tests

Some functions of type f(c::Char) -> Bool

isnumeric('a'), isnumeric('7'), isletter('a')
(false, true, true)

and of type f(s1::String, s2::String) -> Bool

contains("Lampenschirm", "pensch"), startswith("Lampenschirm", "Lamb"), endswith("Lampenschirm", "rm")
(true, false, true)
  • 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.
x = 3
x in [1, 2, 3, 4, 5]
true
x  [1, 2, 33, 4, 5]
false

4.6.3 Logical Operations: &&, ||, !

3 < 4 && !(2 > 8) && !contains("aaa", "b")
true

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
  1. Thus, if test statement end can also be written as test && statement.

  2. Thus, if !test statement end can be written as test || statement.

As an example1 here is an implementation of the factorial function:

function fact(n::Int)
    n >= 0 || error("n must be non-negative")
    n == 0 && return 1
    n * fact(n-1)
end

fact(5)
120

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:

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("All correct!")
end
All correct!
  • 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:
z = 3 < 4 && 10 < 5 &&  sqrt(3^3)
z, typeof(z)
(false, Bool)
z = 3 < 4 && 10 < 50 &&  sqrt(3^3)
z, typeof(z)
(5.196152422706632, Float64)

4.7 Loops

4.7.1 The 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.

i = 1          # typically the test of the 
               # while loop needs preparation ...
while i < 10   
   println(i)
   i += 2      # ... and an update
end
1
3
5
7
9

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.

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("Done!")
i = 1
i = 2
i = 4
i = 5
Done!

With break one can also exit infinite loops:

i = 1

while true
    println(2^i)
    i += 1
    if i > 8 break end
end
2
4
8
16
32
64
128
256

4.7.2 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.

for i  ["Mother", "Father", "Daughter"]
    println(i)
end
Mother
Father
Daughter

A numerical loop counter is often needed. For this purpose, we have the range construct. The simplest forms are Start:End and Start:Step:End.

end_value = 5

for i  1:end_value
    println(i^2)
end
1
4
9
16
25
for i = 1:5.5  print(" $i") end
 1.0 2.0 3.0 4.0 5.0
for i = 1:2:14 print("  $i") end
  1  3  5  7  9  11  13
for k = 14 : -2.5 : 1 print("   $k") end
   14.0   11.5   9.0   6.5   4.0   1.5

Nested Loops

A break ends the innermost loop.

for i = 1:3
    for j = 1:3
        println( (i,j) )
        if j == 2
            break
        end
    end
end
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)

Nested loops can also be combined in a single for statement. Then a break ends the entire loop.

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
(1, 1)
(1, 2)
ImportantImportant: 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.

for i = 1:5
    print(i," ... ")
    i += 2
    println(i)
end
1 ... 3
2 ... 4
3 ... 5
4 ... 6
5 ... 7

The C semantics of for(i=1; i<5; i++) corresponds to the while loop:

i = 1
while i<5
   *loop body*   # here one can also mess with i effectively
   i += 1
end

4.8 Unicode

Julia uses Unicode as its character set. This allows identifiers in 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.

4.9 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:

e = 7
3e
21

Here, the input is interpreted as a floating-point number – and 3E+2 or 3f+2 (Float32) as well.

3e+2
300.0

A space creates clarity:

3e + 2
23

This works:

x = 4
3x + 3
15

…and this does not. 0x, 0o, 0b are interpreted as the beginning of a hexadecimal, octal, or binary constant.

3y + 0x
ParseError:
# Error @ /home/hellmund/Julia/Book26/JuliaBook/chapters/syntax.qmd:580:6
3y + 0x  
#    └┘ ── invalid numeric constant
Stacktrace:
 [1] top-level scope
   @ ~/Julia/Book26/JuliaBook/chapters/syntax.qmd:580
  • There are a few other cases where the very permissive syntax leads to surprises.
Important  = 21
Important! = 42       # identifiers can also contain !
(Important, Important!)
(21, 42)
Important!=88
true

Julia interprets this as the comparison Important != 88.

Again, spaces around operators help:

Important! = 88
Important!
88
  • Operators of the form .*, .+,… have a special meaning in Julia (broadcasting, i.e., vectorized operations).
1.+2.
ParseError:
# Error @ /home/hellmund/Julia/Book26/JuliaBook/chapters/syntax.qmd:612:1
1.+2.
└┘ ── ambiguous `.` syntax; add whitespace to clarify (eg `1.+2` might be `1.0+2` or `1 .+ 2`)
Stacktrace:
 [1] top-level scope
   @ ~/Julia/Book26/JuliaBook/chapters/syntax.qmd:612

Again, spaces create clarity!

1. + 2.
3.0

  1. from the Julia documentation↩︎