"Hello world!""Hello world!"
This chapter has no prerequisites.
Now that we’ve got Julia up and running, let’s see what it can do. In time-honoured fashion, we’ll begin by getting Julia to say ‘Hello world!’’ to us. In the REPL, this is as simple as enclosing the text in double quotation marks " " (making it a string, which Julia knows not to try and evaluate as code), and pressing Enter ⮠:
"Hello world!""Hello world!"
To get rid of the quotation marks around the output, we can use the keyword print, and enclose our phrase in parentheses () immediately after (without a space):
print("Hello world!")Hello world!
A relatively unique feature of Julia is the ability to use a wider range of characters in code than just the normal A-Z, a-z, 0-9 and common punctuation. So let’s update Hello world! to the 21st century:
print("👋🌍❗")👋🌍❗
These emoji can be copied in from elsewhere, but the Julia REPL as well as environments such as VSCode in a .jl file or a Pluto notebook allow tab-completion to type such symbols. If you know the right code for the symbol you want (which will always begin with a backslash \), typing out the name and pressing Tab ⇆ will give the symbol. For instance, the three emoji used here have codes \:wave: for 👋, \:earth_africa: for 🌍, and \:exclamation: for❗.
What else can we type? We can get Julia to do some basic sums, using the symbols +, -, *, /, ^ as add, subtract, multiply, divide, and exponentiate respectively, with parentheses () used to prioritise operations as usual, and otherwise the normal order of operations is assumed:
1 + 23
7*856
5 + (3 - 1/2) * 2055.0
When running multiple lines of code at a time (typically by running a .jl file with multiple lines), only the last output calculated will be displayed:
2^5
5^225
This is a general rule in Julia, whenever code is run, the last line determines what output will be displayed. There are ways of getting around this, though, such as following the final line with a semi-colon, which carries out the calculation as normal, but suppresses the output:
2^5
5^2;Admittedly, this isn’t very useful when only used for arithmetic calculations, but there are plenty more things that we could do in Julia where we don’t need to see the output.
Additionally, using print will display any value that it is asked to print, regardless of where this happens in the code:
print(2^5) # 32
print(5^2) # 253225
We notice here that the two outputs have been printed one after the other, with no gap. This is normal behaviour for print, and can be very useful to print more complicated text. If you want every printed output on a different line, you can use println instead.
We also see here the use of a comment. Anything in the same line following a hash # will be ignored by Julia, so we can write some explanation next to our code to help us remember what it is supposed to do.
Adding comments isn’t particularly useful in the REPL (especially as it is forgotten after the REPL is closed), but in .jl script files it is highly recommended, particularly the longer and more complex a program gets.
Julia is far more that a glorified calculator, however. We’ll start be seeing how we can save data for later, using variables. These are a way to give a data a name, which we can use to refer to that data later. This allows us to write code that performs calculations where we don’t explicitly tell Julia which numbers to use. A value is assigned to a variable using = as follows:
number1 = 11
Names of variables are important to choose carefully. We have a multitude of such characters at our disposal, including non-Latin scripts, and emoji like we’ve seen before in text form (although we are somewhat limited for the first character, which must be a letter or letter-like symbol, e.g not a number). They must also not conflict with any of Julia’s inbuilt keywords, such as print we’ve seen above, or words like if, for, end that we’ll see in later chapters. Most punctuation characters (except _) are also not allowed, as they have other purposes, such as " " for enclosing text and # for comments as we’ve already seen. Moreover, Julia is case-sensitive, so X and x would refer to different things.
Names of variables should be in lowercase, with no spaces (as the space marks the end of the variable name) or underscores (unless they are needed to make the name readable).
αριθμός2️⃣ = 22
We can ask Julia to tell us the value of the variable simply by typing the name of that variable.
number11
We can then use the names in place of the values they represent in sums.
number1 + αριθμός2️⃣3
The value of a variable can be changed by the same means that we gave it a value in the first place.
number1 = "one""one"
We’ll see how ubiquitous the use of variables will be throughout this book, and look in more depth at an important property that they have called scope in Chapter 6.
A function, at its simplest, is a way of performing a predetermined algorithm or calculation without having to laboriously do each step one at a time yourself. Instead, it is known only by its name, and Julia understands references to its name as requests to perform said algorithm. For example, we can use Julia to calculate square roots using the sqrt function, even if we don’t know the ins and out of the calculation needed to do it:
sqrt(2)1.4142135623730951
Or, we can find the position of the first letter t in a sentence:
findfirst('t', "Shall I compare thee to a summer's day?")17
The use of single quotation marks 't' instead of double "t" is deliberate here, as they mean different things.
The way to input data into a function is to write the function name, and follow it with a set of parentheses containing the function arguments, which can be literals (that is, writing the data in literally), or variables (where the value of the variable will be given to the function). If there are multiple inputs, they will need to be separated by commas, such as in the min function which finds the minimum of its inputs:
min(1, 2, 3, 4, 5)1
Indeed, we’ve already met several functions above without even knowing it. print is a function that takes an input and prints the result in the REPL. Although they might not look like it, the mathematical operations are also functions, with the special infix (putting the function name between two arguments) notation used since that’s what we’re used to. It is entirely possible to write such calculations in the standard function format, and they work just the same:
+(1, 2)3
Not only that, but it’s possible to write our own functions to encapsulate bits of code that we want to run again and again, perhaps with different starting values. We’ll see how to do this in Chapter 6.
Functions won’t always give an answer, though. Often, this is because the input is nonsensical for what the function is supposed to be doing, such as:
sqrt("cabbage")ERROR: MethodError: no method matching sqrt(::String)
Running this will give an exception (alternatively called an error), which far from being a bad thing, can help us to track down and fix problems in our code. In this case, the problem is as simple as the fact that Julia doesn’t know how to take the square root of a string of text. This brings us on to looking at the types of data that Julia can handle.
Every piece of data that you give Julia, be it a number, a string of text, a collection of elements, even a block of Julia code, has along with it a label that tells Julia how to interpret it. This is called the type of the data, and we’ve seen many already:
Text enclosed in double quotation marks " " has type String, meant for representing data in the form of text
Individual symbols enclosed in single quotation marks ' ' have type Char, short for character. This is a simpler format than String, being more intrinsically understood by the computer, in fact Strings are little more than sequences of Chars
Numbers have types, although there are many different types for different numbers. Common ones include Int64 for integers and Float64 for decimals. For further discussion of this, see Chapter 3
Comma-separated values surrounded by parentheses, as used in the inputs to a function, form a Tuple. Tuples are covered in more depth in Chapter 9, along with many other types representing collections of several values
Functions themselves also have types, although they are slightly more complicated. We can use the typeof function to query types, and as we can see, the sqrt function has type typeof(sqrt):
typeof(sqrt)typeof(sqrt) (singleton type of function sqrt, subtype of Function)
As the bizarre result above suggests, types have many intricacies to them. They are related by the type graph, which groups together types in various categories as a tree structure. We’ll see examples of this in Chapter 3 and Chapter 9, and dive into the details in Chapter 7. We’ll also define our own types in Chapter 6. For now though, we can just think of them as a way for Julia to distinguish what sort of data we give it, and how it should be processed and stored.
The last structure that we’ll introduce is the macro. At a basic level, a macro can be thought of as just another type of function, although there are important differences. In general, a macro is used by typing @, followed by its name, and then some lines of code. For instance:
@time macro runs the code that we give it, but also outputs how long it took to run:@time 123456789 * 987654321 0.000001 seconds
121932631112635269
@doc macro can be used in place of Julia’s REPL help mode, which we’ll meet shortly:@doc π π
pi
The constant pi.
Unicode π can be typed by writing \pi then pressing tab in the Julia REPL,
and in many editors.
See also: sinpi, sincospi, deg2rad.
Examples
≡≡≡≡≡≡≡≡≡≡
julia> pi
π = 3.1415926535897...
julia> 1/2pi
0.15915494309189535
@assert macro causes an error if the condition following it is false, which can be useful for debugging:@assert 3 > 5ERROR: AssertionError: 3 > 5
We’ll meet a few other macros throughout the book, as well as touching on the Expr types that macros deal in. There’s a lot more to be said about macros that we won’t cover, however, such as writing custom macros.
Whether you’re just getting started, or are very experienced, it can sometimes be difficult to fix problems or remember how certain functions work. For this purpose, Julia has a number of ways to provide guidance to put you back on track.
We’ve already seen tab-completion to type non-standard characters, like emoji. In fact, tab-completion does more than this, it can complete (hence the name) partially typed keywords or variable names. For example, typing sq in the REPL and then pressing Tab ⇆ gives the word sqrt, as it is the only keyword that Julia knows beginning with sqrt. However, if we define a variable:
squid = "🦑""🦑"
then pressing Tab ⇆ once doesn’t do anything, because it doesn’t know which one of sqrt and squid we want. Pressing Tab ⇆ again won’t complete it, but it will give us a list of the possibilites that we might mean, so that we can work out which we meant.
If there are multiple options, but they all share some characters, Julia will complete as much as possible, e.g. typing prin and pressing Tab ⇆ will give print, as the possible options are print, println, and printstyled, all of which have t as the next letter.
This can be very useful if you can’t remember the exact name of a variable or function, or just if you’re exploring what functions exist inbuilt into Julia. Other environments than the REPL like the VSCode editor or Pluto will provide similar tab-completion functionality.
help? in the REPLPressing ? in the REPL changes the prompt from the green julia> to the yellow help?>, entering help mode. The functionality of the REPL now changes dramatically, from executing inputs as code, to giving information about the keywords and functions used. For example, if we want to learn more about the function +, we can enter help mode and type +, or some code including + such as 2 + 3. This gives the following result:
help?> 2 + 3
+(x, y...)
Addition operator. x+y+z+... calls this function with all
arguments, i.e. +(x, y, z, ...).
Examples
≡≡≡≡≡≡≡≡≡≡
julia> 1 + 20 + 4
25
julia> +(1, 20, 4)
25
Help mode can also give similar names that it knows if you’ve mistyped the input:
help?> min
search: min minmax minimum minimum! argmin Main typemin findmin
Pasting in a Unicode symbol will give you the code to tab-complete and type the symbol (if a code exists for that symbol):
help?> 🍄
"🍄" can be typed by \:mushroom:<tab>
To exit help mode, you can press ← Backspace on an empty line.
When an exception occurs, Julia provides an error message, which although a little intimidating at first with the red ERROR: text, but actually is very useful in diagnosing issues.
Firstly, the type of error is given, which can tell you immediately what the problem is in simple cases. For instance, a MethodError may occur when the inputs to a function have the wrong types, or an UndefVarError if a word is used that isn’t recognised as a variable name. In the REPL, this can often be enough to find the mistake, but in more complex programs, with custom functions and modules, it may not suffice.
Instead, we look to the Stacktrace section. It provides a list of all of the functions that have been called, with the error occurring in the first function listed, which itself was called by the second function listed, and so on. Line numbers are also provided, so if unexpected behaviour is happening, it can be traced down exactly to the line or lines of code that caused it.
We’ll see plenty more errors in this book (hopefully only the deliberate ones though!), and will look into the ways that exceptions can be used to aid programming in Chapter 12.