5
5
Darren Irwin
On this page we’ll take an introductory tour of Julia. This will touch on a variety of topics, providing just enough detail to get you going. (We’ll go more in-depth on various topics later. You can also learn more from the Julia Manual.)
Now that we have Julia running (on the last page), we can learn how to enter expressions that Julia will evaluate.
The simplest expressions are just a single number or string of text, e.g. try entering something like:
After you type 5
at the julia>
prompt and press enter, Julia responds with 5
, the returned value of your expression.
You can do a little more complex expression:
Play around with other simple mathematical expressions. These are some of the main arithmetic operators, which you are likely familiar with: +
, -
, *
, /
, ^
. Try using parentheses to control the order of operations (for example: compare the returned value of 3 + 5 / 2^3
and (3 + 5) / 2^3
).
One that operator that you might be less familiar with is the remainder operator %
which gives the remainder of a division. Try it out:
Another is the ÷
operator, which produces just the integer part of a division (without the decimal part). You aren’t likely to use ÷
very often, but I include it here as an example of Julia’s use of unicode characters (more on this below). To type that character at the Julia prompt, enter \div
followed by the tab
key.
We can also use some functions in our expressions. A function is something that does a specific thing defined by code already loaded into memory. You can recognize a function in Julia by the fact that it will be a string of text followed immediately by parentheses, perhaps with inputs to the function inside the parentheses. Here we use the sqrt()
function, which produces a square root of the number in the parentheses.
To look up information about a command in Julia, you can enter the help mode by typing ?
at the Julia prompt. The prompt then changes to help?>
, and you can type the name of a function or operator and then enter. Julia will then tell you some helpful info. Try this for the +
operator, for instance.
Each of the above expressions returns a value that we can give a name and store in memory, using the equal sign =
, the assignment operator. For example:
The above command does several interesting things:
x
). If not, it creates that object.We can then use that named object’s value in subsequent expressions:
Assigning values to named objects allows us to save values in memory and then combine them in our calcuations:
The semicolons above are used to separate distinct expressions that Julia should evaluate before continuing to the next. This is called a compound expresssion and is a convenient to enter several commands on the same line. Another way to enter a compound expression is with a begin-end block:
Pick a number for the radius (in km) of a circle, and assign that to a variable name and store that in Julia’s memory. Then in a separate command, use that variable to calculate the area of the circle (the standard formula for this is π\(r^{2}\); to write the exponent of ‘2’ though, you’ll need to use ‘^2’). A neat thing is that Julia already knows the value of π; to get it, just type pi
or alternatively \pi
then tab.
When computers store numbers, they can do it in different ways, each with their advantages and disadvantages. Two main ways that Julia can store numbers are as integers or as floating-point numbers. Integers have exact values but are quite limited in the values they can take, whereas floating-point numbers can take many more values but have a disadvantage that they are not exact.
We can learn about the Type that Julia uses to store values by using the typeof()
function:
Julia responds by saying that the value 3
is of the Int64
type. This means that it is an integer stored in memory using 64 bits. (No worries if you don’t know what this means.) Now compare to this:
Julia responds by sayng 3.0
is of the Float64
type. This means a 64-bit floating-point number. By writing the .0
after the 3
we have told Julia we want this number treated as a floating-point number.
Much of the time, we don’t need to think about variable types, because Julia is smart and handles types through something called dynamic typing. Here’s an example:
Above, Julia figures that by entering b = 3.0
you are indicating that you want b
treated as a floating-point number, so when it adds the integer 2 to this, it returns a floating-point number.
Julia has all sort of other useful Types of numbers. One that I will point out here is the Rational
number type, which you can construct with the //
operator and do exact calculations using ratios:
As your Julia skills increase, there are large benefits to being somewhat aware of types; this can help you write efficient programs and help with debugging your code. You can even define your own Types.
Julia has a neat data structure (i.e. a type) to store arithmetic series of numbers:
Julia stores this range efficiently, until the actual numbers are needed. If we want to actually convert it to the numbers themselves, we could enter something like collect(rangeOfNums)
If we want only the even numbers, we enter something like collect(0:2:30)
.
You have a population of 30 individuals that is decreasing by 3 individuals in each time step. Use the collect()
function to create a list of numbers (a.k.a. a vector) representing the population sizes at each time step (until the population goes extinct).
Programming in biology often involves manipulating text such as “ACGT”. For this, we can use two other variable types in Julia: characters (officially Char
) and strings (officially String
), which are usually made up of a series of characters (imagine beads of letters on a string, ha ha).
We enter characters with single quotes, and can combine them to make strings:
Above, we create two variables of type Char
and combined them (yes, Julia views combining characters or strings as a form of multiplication, ha ha) to produce a String
.
If Julia uses the *
symbol for combining two characters or strings into a longer string, what do you think it might use the ^
symbol for, when applied to a character or string? Make a guess, and then play around and find out. (If you are stuck, try entering 'T'^2
and think about what the REPL returns)
When you enter a String
into Julia, you need to use double quotes (unlike a Char
with single quotes).
The function string
above concatenates different strings together.
Assign a few of your favorite species to different variable names, and then combine them into a single string, with commas separating their names.
One fun thing about Julia is that you can use Unicode characters in object names, strings, function names, and some operators. The motivator for this is to make code read more like humans tend to write mathematics. For example:
To make the Unicode symbols above, you would type \chi
tab \^2
tab, and on the next line \beta
tab. Notice also that the expression 2χ²
is evaluated the same as 2*χ²
or 2 * χ²
.
Unicode also allows some fun.
We can also use Unicode symbols in variable names:
Note the use of the #
symbol to make comments to the right of code.
You can find how to write a whole bunch of Unicode symbols by clicking here
In programming it is often important to check if certain conditions are true
or false
. These values are called Booleans (of type Bool
in Julia).
These comparison operators are used to compare two values (place to left and right of the operator), resulting in true
or false
:
==
: equal? (notice the two equal symbols)
!=
: not equal? (or try a uncode symbol by typing then tab) <
: less than?
<=
: less than or equal?
>
: greater than?
>=
: greater than or equal?
Try a bunch of comparisons of values using the above. These are called Boolean expressions. For example:
We can use Boolean operators to combine multiple comparisons. These are used as follows, where x
and y
are Boolean expressions:
!x
: not x
x && y
: x and y
x || y
: x or y
For example:
The expression rand()
gets a random number between 0 and 1. Write a series of commands that picks such a random number, and then returns true
if that number is either above 0.75 or below 0.25, and false
otherwise.
(If you solve the above and have time, can you figure out a way to chain your Boolean expressions into a form similar to value1 < x < value2
?)
Objects can contain more than a single item in them. A general term for such a data structure is a collection. An array is a common type of collection used in Julia: It can be thought of as an n-dimensional lattice of boxes, where you can store something in each box. Below we create some kinds of arrays:
Because these variables are more complex data structures that the simple ones we’ve looked at so far, Julia tells you the type of the returned value, before showing you the actual values.
Note that a Vector
is another name (an alias) for a 1-dimensional array, and a Matrix
is another name for a 2-dimensional array. Inside the curly brackets, Julia indicates the type that each element of the array belongs to (this is Float64
in the first case, and Int64
in the second).
We can ask Julia to return the values in parts of an array by indexing into the array. For example:
What do you think using begin
as an index would do? Try it!
We can even use such indexing to change the value stored one of the “boxes” in an array:
We’re going to eventually learn a lot more about arrays, as they are super useful in data science, bioinformatics, and simulations. There are many Julia functions than manipulate arrays. Here we’ll mention two of them that are useful for working with vectors (1-dimensional arrays) when you are starting out:
push!()
and pop!()
These two function are used to add (push) an element to the end of a vector, or remove (pop) the last element from a vector:
4-element Vector{String}:
"one"
"two"
"three"
"four"
We added an element to the end. Now we can remove it:
That last one returns the last element of vecA
, and removes it from vecA
:
The !
in these function names conveys that these functions change an object that is being passed to the function (in this case, vecA
). Other useful and well-named functions that change vectors include insert!()
, delete!()
, append!()
, empty!()
, and many others.
Another kind of collection is called a tuple. It is a lot like a vector (a 1-dimensional array) but is immutable. This means that once defined, you cannot change specific values stored in a particular tuple. We create them using parentheses and commas:
The response tells us that the object we created is of Tuple
type, and it tells us the types of each element in the tuple.
We can also create named tuples in which we give names to the values:
We can get the value of a certain element by using that name, like this:
The above is one of the uses of the “.
” symbol, to access a named element within a tuple. (Another use, quite different, is described after the next heading below).
Tuples are useful for storing and calling fixed bits of info together. Because they are not mutable, Julia can store and use them in a more efficient way than arrays. They are also used when calling functions (we’ll explore this later).
We often want to apply an operation or function to each element of an array. In Julia this is called “broadcasting” and is accomplished by the humble “dot” operator: .
We can put this dot in front of any arithmetic operator to make the operator apply to each element of a collection:
We can also put this dot right after a function to have the function apply to each element in a collection:
Macros are a kind of function that takes text as input and converts it to code that is then evaluated. They are a somewhat advanced topic in terms of developing a full undertanding. For now, I just want to mention them because some are hugely useful even when starting out. As an example, if you are doing a lot of “dot” operations, you can use the @.
macro to convert all the operators in a line to dot operators:
The line with the @.
macro produces the same output as:
Another useful macro is @time
:
The rand(x)
function produces x
random numbers between 0 and 1. The above returns the sum of 1000 such numbers. Julia tells you the time it took, followed by the sum.
Modify the above code so that it takes the square of each of one million random numbers between 0 and 1, and then adds them up.
Julia facilitates the writing of your own functions. This can have huge benefits in terms of organizing your programs and making them efficient. There are several ways to define them:
We’ve defined that function. Now we can use it:
0.038686 seconds (6 allocations: 15.259 MiB, 69.99% gc time)
666689.9455230328
Below demonstrates another way to write functions, in this case with two arguments (things you pass into the function, called parameters inside the function):
Try calling that function a few times, with different values of x
and y
, e.g.:
Can you figure out how to call the above function in a way where you can input a series of numbers as the first argument (for example, x = [1, 3, 7]
), and -2
as the second argument? (Hint: if you get an error, reviewing the Broadcasting section above might help)
Thousands of people around the world have contributed over 10,000 packages that extend functionality of Julia. Installing these packages is easy.
First, enter the package mode of the REPL by typing the right square bracket symbol ]
. You will then see the julia>
prompt change into a prompt that looks something like (@v1.11) pkg>
. Then, type a command to add a package, e.g.:
This tells Julia to download and install the officially registered package with that name. The Plots package is a big one, so this can take some time.
To get out of package mode, press the “delete” key and this will return you to the normal Julia REPL mode.
To actually use the Plots package, we need to load it into the memory for this Julia session. To do that, simply write:
Now, let’s use a function called plot()
that is included in this Plots package. Try:
This will likely open another window on your computer, and show a plot. (If not, don’t worry; we’re going to set up another good way to plot in the next page.)
If the above worked, you can see another way to run the plot() function here:
In our first use of the plot() function above, we passed in two argument, both vectors of numbers. In the second, we passed in just a mathematical function, and plot() figured out a good way to show us the mathematical relationship represented by that function.
This flexibility in function calls is an example of multiple dispatch, a key feature of Julia. We’ll come back to this topic in the future.
We’ve now learned a lot of basic concepts related to Julia programming, and it is now time to put them together in more complex ways. For that, we’ll learn how to use Pluto notebooks on the next page. This will enable to write longer programs and organize and save them.