# Getting started with Julia

Because JuMP is embedded in Julia, knowing some basic Julia is important before you start learning JuMP.

Tip

This tutorial is designed to provide a minimalist crash course in the basics of Julia. You can find resources that provide a more comprehensive introduction to Julia here.

## Installing Julia

Tip

Unless you know otherwise, you probably want the 64-bit version.

Next, you need an IDE to develop in. VS Code is a popular choice, so follow these install instructions.

Julia can also be used with Jupyter notebooks or the reactive notebooks of Pluto.jl.

## The Julia REPL

The main way of interacting with Julia is via its REPL (Read Evaluate Print Loop). To access the REPL, start the Julia executable to arrive at the julia> prompt, and then start coding:

julia> 1 + 12

As your programs become larger, write a script as a text file, and then run that file using:

julia> include("path/to/file.jl")
Warning

Because of Julia's startup latency, running scripts from the command line like the following is slow:

julia> x = 123123julia> println("The value of x is: $(x)")The value of x is: 123 Use triple-quotes for multiline strings: julia> s = """ Here is a multiline string """"Here is\na\nmultiline string\n"julia> println(s)Here is a multiline string ### Symbols Julia Symbols are a data structure from the compiler that represent Julia identifiers (i.e., variable names). julia> println("The value of x is:$(eval(:x))")The value of x is: 123
Warning

We used eval here to demonstrate how Julia links Symbols to variables. However, avoid calling eval in your code. It is usually a sign that your code is doing something that could be more easily achieved a different way. The Community Forum is a good place to ask for advice on alternative approaches.

julia> typeof(:x)Symbol

You can think of a Symbol as a String that takes up less memory, and that can't be modified.

Convert between String and Symbol using their constructors:

julia> String(:abc)"abc"julia> Symbol("abc"):abc
Tip

Symbols are often (ab)used to stand in for a String or an Enum, when one of the latter is likely a better choice. The JuMP Style guide recommends reserving Symbols for identifiers. See @enum vs. Symbol for more.

### Tuples

Julia makes extensive use of a simple data structure called Tuples. Tuples are immutable collections of values. For example:

julia> t = ("hello", 1.2, :foo)("hello", 1.2, :foo)julia> typeof(t)Tuple{String, Float64, Symbol}

Tuples can be accessed by index, similar to arrays:

julia> t[2]1.2

And they can be "unpacked" like so:

julia> a, b, c = t("hello", 1.2, :foo)julia> b1.2

The values can also be given names, which is a convenient way of making light-weight data structures.

julia> t = (word = "hello", num = 1.2, sym = :foo)(word = "hello", num = 1.2, sym = :foo)

Values can be accessed using dot syntax:

julia> t.word"hello"

## Dictionaries

Similar to Python, Julia has native support for dictionaries. Dictionaries provide a very generic way of mapping keys to values. For example, a map of integers to strings:

julia> d1 = Dict(1 => "A", 2 => "B", 4 => "D")Dict{Int64, String} with 3 entries:
4 => "D"
2 => "B"
1 => "A"
Info

Type-stuff again: Dict{Int64,String} is a dictionary with Int64 keys and String values.

Looking up a value uses the bracket syntax:

julia> d1[2]"B"

Dictionaries support non-integer keys and can mix data types:

julia> Dict("A" => 1, "B" => 2.5, "D" => 2 - 3im)Dict{String, Number} with 3 entries:
"B" => 2.5
"A" => 1
"D" => 2-3im
Info

Julia types form a hierarchy. Here the value type of the dictionary is Number, which is a generalization of Int64, Float64, and Complex{Int}. Leaf nodes in this hierarchy are called "concrete" types, and all others are called "Abstract." In general, having variables with abstract types like Number can lead to slower code, so you should try to make sure every element in a dictionary or vector is the same type. For example, in this case we could represent every element as a Complex{Float64}:

julia> Dict("A" => 1.0 + 0.0im, "B" => 2.5 + 0.0im, "D" => 2.0 - 3.0im)Dict{String, ComplexF64} with 3 entries:
"B" => 2.5+0.0im
"A" => 1.0+0.0im
"D" => 2.0-3.0im

Dictionaries can be nested:

julia> d2 = Dict("A" => 1, "B" => 2, "D" => Dict(:foo => 3, :bar => 4))Dict{String, Any} with 3 entries:
"B" => 2
"A" => 1
"D" => Dict(:bar=>4, :foo=>3)julia> d2["B"]2julia> d2["D"][:foo]3

## Structs

You can define custom datastructures with struct:

julia> struct MyStruct
x::Int
y::String
z::Dict{Int,Int}
endjulia> a = MyStruct(1, "a", Dict(2 => 3))Main.MyStruct(1, "a", Dict(2 => 3))julia> a.x1

By default, these are not mutable

julia>     a.x = 2setfield! immutable struct of type MyStruct cannot be changed

However, you can declare a mutable struct which is mutable:

julia> mutable struct MyStructMutable
x::Int
y::String
z::Dict{Int,Int}
endjulia> a = MyStructMutable(1, "a", Dict(2 => 3))Main.MyStructMutable(1, "a", Dict(2 => 3))julia> a.x1julia> a.x = 22julia> aMain.MyStructMutable(2, "a", Dict(2 => 3))

## Loops

Julia has native support for for-each style loops with the syntax for <value> in <collection> end:

julia> for i in 1:5
println(i)
end1
2
3
4
5
Info

Ranges are constructed as start:stop, or start:step:stop.

julia> for i in 1.2:1.1:5.6
println(i)
end1.2
2.3
3.4
4.5
5.6

This for-each loop also works with dictionaries:

julia> for (key, value) in Dict("A" => 1, "B" => 2.5, "D" => 2 - 3im)
println("$(key):$(value)")
endB: 2.5
A: 1
D: 2 - 3im

Note that in contrast to vector languages like Matlab and R, loops do not result in a significant performance degradation in Julia.

## Control flow

Julia control flow is similar to Matlab, using the keywords if-elseif-else-end, and the logical operators || and && for or and and respectively:

julia> for i in 0:5:15
if i < 5
println("$(i) is less than 5") elseif i < 10 println("$(i) is less than 10")
else
if i == 10
println("the value is 10")
else
println("$(i) is bigger than 10") end end end0 is less than 5 5 is less than 10 the value is 10 15 is bigger than 10 ## Comprehensions Similar to languages like Haskell and Python, Julia supports the use of simple loops in the construction of arrays and dictionaries, called comprehensions. A list of increasing integers: julia> [i for i in 1:5]5-element Vector{Int64}: 1 2 3 4 5 Matrices can be built by including multiple indices: julia> [i * j for i in 1:5, j in 5:10]5×6 Matrix{Int64}: 5 6 7 8 9 10 10 12 14 16 18 20 15 18 21 24 27 30 20 24 28 32 36 40 25 30 35 40 45 50 Conditional statements can be used to filter out some values: julia> [i for i in 1:10 if i % 2 == 1]5-element Vector{Int64}: 1 3 5 7 9 A similar syntax can be used for building dictionaries: julia> Dict("$(i)" => i for i in 1:10 if i % 2 == 1)Dict{String, Int64} with 5 entries:
"1" => 1
"5" => 5
"7" => 7
"9" => 9
"3" => 3

## Functions

A simple function is defined as follows:

julia> function print_hello()
return println("hello")
endprint_hello (generic function with 1 method)julia> print_hello()hello

Arguments can be added to a function:

julia> function print_it(x)
return println(x)
endprint_it (generic function with 1 method)julia> print_it("hello")hellojulia> print_it(1.234)1.234julia> print_it(:my_id)my_id

Optional keyword arguments are also possible:

julia> function print_it(x; prefix = "value:")
return println("$(prefix)$(x)")
endprint_it (generic function with 1 method)julia> print_it(1.234)value: 1.234julia> print_it(1.234; prefix = "val:")val: 1.234

The keyword return is used to specify the return values of a function:

julia> function mult(x; y = 2.0)
return x * y
endmult (generic function with 1 method)julia> mult(4.0)8.0julia> mult(4.0; y = 5.0)20.0

### Anonymous functions

The syntax input -> output creates an anonymous function. These are most useful when passed to other functions. For example:

julia> f = x -> x^2#11 (generic function with 1 method)julia> f(2)4julia> map(x -> x^2, 1:4)4-element Vector{Int64}:
1
4
9
16

### Type parameters

We can constrain the inputs to a function using type parameters, which are :: followed by the type of the input we want. For example:

julia> function foo(x::Int)
return x^2
endfoo (generic function with 1 method)julia> function foo(x::Float64)
return exp(x)
endfoo (generic function with 2 methods)julia> function foo(x::Number)
return x + 1
endfoo (generic function with 3 methods)julia> foo(2)4julia> foo(2.0)7.38905609893065julia> foo(1 + 1im)2 + 1im

But what happens if we call foo with something we haven't defined it for?

julia>     foo([1, 2, 3])MethodError: no method matching foo(::Vector{Int64})
Closest candidates are:
foo(!Matched::Int64) at REPL[1]:1
foo(!Matched::Float64) at REPL[2]:1
foo(!Matched::Number) at REPL[3]:1

We get a dreaded MethodError! A MethodError means that you passed a function something that didn't match the type that it was expecting. In this case, the error message says that it doesn't know how to handle an Vector{Int64}, but it does know how to handle Float64, Int64, and Number.

Tip

Read the "Closest candidates" part of the error message carefully to get a hint as to what was expected.

In the example above, we didn't define what to do if f was passed a Vector. Luckily, Julia provides a convenient syntax for mapping f element-wise over arrays! Just add a . between the name of the function and the opening (. This works for any function, including functions with multiple arguments. For example:

julia> f.([1, 2, 3])3-element Vector{Int64}:
1
4
9
Tip

Get a MethodError when calling a function that takes a Vector, Matrix, or Array? Try broadcasting it!

## Mutable vs immutable objects

Some types in Julia are mutable, which means you can change the values inside them. A good example is an array. You can modify the contents of an array without having to make a new array.

In contrast, types like Float64 are immutable. You cannot modify the contents of a Float64.

This is something to be aware of when passing types into functions. For example:

julia> function mutability_example(mutable_type::Vector{Int}, immutable_type::Int)
mutable_type[1] += 1
immutable_type += 1
return
endmutability_example (generic function with 1 method)julia> mutable_type = [1, 2, 3]3-element Vector{Int64}:
1
2
3julia> immutable_type = 11julia> mutability_example(mutable_type, immutable_type)julia> println("mutable_type: $(mutable_type)")mutable_type: [2, 2, 3]julia> println("immutable_type:$(immutable_type)")immutable_type: 1

Because Vector{Int} is a mutable type, modifying the variable inside the function changed the value outside of the function. In contrast, the change to immutable_type didn't modify the value outside the function.

You can check mutability with the isimmutable function:

julia> isimmutable([1, 2, 3])falsejulia> isimmutable(1)true

## The package manager

### Installing packages

No matter how wonderful Julia's base language is, at some point you will want to use an extension package. Some of these are built-in, for example random number generation is available in the Random package in the standard library. These packages are loaded with the commands using and import.

julia> using Random  # The equivalent of Python's from Random import *julia> import Random  # The equivalent of Python's import Randomjulia> Random.seed!(33)MersenneTwister(33)julia> [rand() for i in 1:10]10-element Vector{Float64}:
0.8245577112736127
0.2928364052074266
0.8765793121770682
0.41615145984974955
0.7113242552761618
0.7762718106176869
0.407423649552187
0.15761624576044575
0.8889767003637221
0.017829104289712516

The Package Manager is used to install packages that are not part of Julia's standard library.

For example the following can be used to install JuMP,

using Pkg
Pkg.add("JuMP")

For a complete list of registered Julia packages see the package listing at JuliaHub.

From time to you may wish to use a Julia package that is not registered. In this case a git repository URL can be used to install the package.

using Pkg
Pkg.add("https://github.com/user-name/MyPackage.jl.git")

### Package environments

By default, Pkg.add will add packages to Julia's global environment. However, Julia also has built-in support for virtual environments.

Activate a virtual environment with:

import Pkg; Pkg.activate("/path/to/environment")

You can see what packages are installed in the current environment with Pkg.status().

Tip

We strongly recommend you create a Pkg environment for each project that you create in Julia, and add only the packages that you need, instead of adding lots of packages to the global environment. The Pkg manager documentation has more information on this topic.

Tip