# Julia Overview

Julia is a language for scientific computing that has similar features as Matlab and Python, but can usually (via automatic just-in-time compilation) achieve performance close to C/C++.

Similarly to Python, it can be used interactively on the terminal, for executing script files, or via notebooks. The basic language and the accompanying tools are free software.

This is a brief overview of syntax and capabilities. A complete documentation can be found here.

## Basics

Built-in capabilities for handling matrices and vectors similar to Matlab

In [1]:
A = [4.  -1.; 1.  2.]

Out[1]:
2×2 Array{Float64,2}:
4.0  -1.0
1.0   2.0
In [2]:
b = [1., 2.]

Out[2]:
2-element Array{Float64,1}:
1.0
2.0
In [3]:
x = A\b   # solve A*x = b

Out[3]:
2-element Array{Float64,1}:
0.444444
0.777778

Unlike Matlab, Julia differentiates between various basic types:

• Floating point: Float16, Float32, Float64 (default)
• Signed integers: Int16, Int32, Int64 (default, alias Int), Int128
• Unsigned integers: UInt16, UInt32, UInt64 (default, alias UInt), UInt128
• Bool
• Complex numbers, rational numbers
• Arbitrary-precision types: BigInt, BigFloat
• Char, String
In [4]:
i = 103491;
typeof(i)

Out[4]:
Int64
In [5]:
a = 1. + 3im
b = complex(2., 1.)
a/b

Out[5]:
1.0 + 1.0im
In [6]:
n = 17
c = 's'
s = "tip "*string(n)*": string"*string(c)*" are concatenated with *"

Out[6]:
"tip 17: strings are concatenated with *"

Argument types are strictly enforced:

In [7]:
log(-1.)

LoadError: DomainError:
while loading In[7], in expression starting on line 1

in nan_dom_err at ./math.jl:196 [inlined]
in log(::Float64) at ./math.jl:202
In [8]:
log(complex(-1))

Out[8]:
0.0 + 3.141592653589793im

Arbitrary unicode characters are allowed as identifiers (entered, e.g., via \lambda [TAB], A\^- [TAB] \^1 [TAB]) - see also the character list

In [9]:
λ, Ψ = eig(A);  # eigenvalue decomposition, with two separate return values
A⁻¹= inv(A);


General note: In the interactive terminal, to display the help for a specific function, type "?" and then its name (e.g., "eig")

## Control flow

In [10]:
c = rand();
if c <= 0.49999
elseif c >= 0.50001
println("tails");
else
println("side");
end

heads

In [11]:
(rand() <= 0.5 ? "heads" : "tails")

Out[11]:
"heads"
In [12]:
for i = 1:10
print(i, " ");
end

1 2 3 4 5 6 7 8 9 10
In [13]:
p = 0;
while p < 3 && p > -5
p += rand([-1,1]);
println(p, "  ", abs(p), "  ", sign(p));
end

1  1  1
0  0  0
-1  1  -1
0  0  0
-1  1  -1
0  0  0
1  1  1
0  0  0
-1  1  -1
-2  2  -1
-1  1  -1
-2  2  -1
-1  1  -1
0  0  0
1  1  1
2  2  1
3  3  1


## Advanced data structures

### Arrays

Matrices and vectors are of Array type. Note that indexing is 1-based.

In [14]:
y = zeros(size(A,2));
for i = 1:size(A,1)
for j = 1:size(A,2)
y[i] += A[i,j] * x[j];
end
end
y

Out[14]:
2-element Array{Float64,1}:
1.0
2.0

Support for Array-based operations is similar to Matlab:

In [15]:
x = linspace(0., 1., 20);
x.^4 .* exp(-x.^2/2)

Out[15]:
20-element Array{Float64,1}:
0.0
7.66274e-6
0.000122095
0.000613843
0.00192133
0.00463263
0.00946098
0.0172149
0.028764
0.0450021
0.0668089
0.0950108
0.130344
0.173421
0.224699
0.284452
0.352758
0.42948
0.514262
0.606531   
In [16]:
A = [1. 2. ; 3. 4.]

Out[16]:
2×2 Array{Float64,2}:
1.0  2.0
3.0  4.0
In [17]:
A[1,:]

Out[17]:
2-element Array{Float64,1}:
1.0
2.0
In [18]:
A[:,1] = [5, 6];
A

Out[18]:
2×2 Array{Float64,2}:
5.0  2.0
6.0  4.0

Arrays can also hold other types (e.g., other Arrays). Note the syntax Type{T} for parameterized types (similar to templates in C++, generics in Java and recent versions of Python)

In [19]:
arr1 = Array{Array{Float64,1},2}(2,2)

Out[19]:
2×2 Array{Array{Float64,1},2}:
#undef  #undef
#undef  #undef
In [20]:
arr1[1,1] = Array{Float64,1}();
arr1[1,2] = Float64[];
arr1[2,1] = Array{Float64,1}([1., 2.]);
arr1[2,2] = Float64[2., 3.];
arr1

Out[20]:
2×2 Array{Array{Float64,1},2}:
Float64[]  Float64[]
[1.0,2.0]  [2.0,3.0]

Even mixed Arrays are possible (but are usually not best for performance):

In [21]:
arr2 = [1, 1., "1", [1.]]

Out[21]:
4-element Array{Any,1}:
1
1.0
"1"
[1.0]

Arrays are always handled by reference, copies need to be requested specifically:

In [22]:
a = [1., 1.];
b = a;  # b references the same array as a
c = copy(a); # c is a copy of a
b[1] = 2.;
println("a = ", a, "\nb = ", b, "\nc = ", c)

a = [2.0,1.0]
b = [2.0,1.0]
c = [1.0,1.0]


### Tuples

Tuple is an array type that is indexed similarly as a 1D Array. They are initialized with round brackets or simply by a comma-separated list. Tuples are, however, immutable: they cannot be modified after creation.

In [23]:
t = (1, 2)

Out[23]:
(1,2)
In [24]:
t = 1, 2

Out[24]:
(1,2)
In [25]:
x, y = 1, 2;   # same as (x,y) = (1,2)
println("x = ", x, ", y = ", y);

x = 1, y = 2

In [26]:
y, x = x, y;  # swap x and y
println("x = ", x, ", y = ", y);

x = 2, y = 1


### Sets

In [27]:
MixedSet = Set();
push!(MixedSet,"abc");
push!(MixedSet,π);
push!(MixedSet,9);
MixedSet

Out[27]:
Set(Any["abc",9,π = 3.1415926535897...])
In [28]:
π in MixedSet

Out[28]:
true
In [29]:
SparseIntSet = Set{Int}([-2411, 1022981,9]);
push!(SparseIntSet, 3);
for i ∈ SparseIntSet
if i ∈ MixedSet
println(i);
end
end

9

In [30]:
DenseIntSet = IntSet([1,3,5,7,11])  # implemented by bit vectors, for non-sparse sets

Out[30]:
IntSet([1, 3, 5, 7, 11])
In [31]:
union(SparseIntSet,DenseIntSet)  # analogously: intersect, symdiff, ...

Out[31]:
8-element Array{Int64,1}:
9
1022981
3
-2411
1
5
7
11

### Dictionaries

In [32]:
D = Dict{Tuple{Int,Int},Float64}((1,2)=>π, (1,0)=>e);
D[(9,9)] = 0.;
D

Out[32]:
Dict{Tuple{Int64,Int64},Float64} with 3 entries:
(1,2) => 3.14159
(1,0) => 2.71828
(9,9) => 0.0
In [33]:
(1,1) ∈ keys(D)

Out[33]:
false
In [34]:
D[(1,0)]

Out[34]:
2.718281828459045

## Iterating over collections and other objects

Using the syntax for i = I (or equivalently for i in I or for i ∈ I) one can iterate over any object for which the functions
start(I) → state, done(I, state) → Bool, next(I, state) → (item, state)
are available, see the documentation. In particular, this applies to the built-in containers:

In [35]:
A = [1., 2., 3.];
for a ∈ A
print(a, "  ");
end
println("");
B = IntSet([13, 5, 2, 9]);
for b ∈ B
print(b, "  ");
end

1.0  2.0  3.0
2  5  9  13  

The range notation 1:n does not create a vector as in Matlab, but a UnitRange object that can be iterated over (but needs only constant memory!)

In [36]:
typeof(1:10)

Out[36]:
UnitRange{Int64}
In [37]:
typeof(0:0.1:1)

Out[37]:
FloatRange{Float64}

To obtain an actual vector from any iterable, one can use collect

In [38]:
collect(1:5)

Out[38]:
5-element Array{Int64,1}:
1
2
3
4
5

There are some useful built-in functions for generating new iterable objects from collections.

In [39]:
for (i, x) in enumerate(B)
println(i, ":  ", x);
end

1:  2
2:  5
3:  9
4:  13

In [40]:
V = [1, 2, 3]; W = [4, 5, 6];
for (x,y) in zip(V, W)
println(x, ", ", y);
end

1, 4
2, 5
3, 6


A similar syntax can be used in array comprehensions (similar to Python) and in generator expressions:

In [41]:
[n^2 for n=1:5]

Out[41]:
5-element Array{Int64,1}:
1
4
9
16
25
In [42]:
[1./(i+j) for i = 1:3, j = 1:6]

Out[42]:
3×6 Array{Float64,2}:
0.5       0.333333  0.25      0.2       0.166667  0.142857
0.333333  0.25      0.2       0.166667  0.142857  0.125
0.25      0.2       0.166667  0.142857  0.125     0.111111
In [43]:
sum(i for i=1:10)

Out[43]:
55
In [44]:
Dict(x => sin(π*x) for x in 0:.5:2.)

Out[44]:
Dict{Float64,Float64} with 5 entries:
0.0 => 0.0
0.5 => 1.0
2.0 => -2.44929e-16
1.5 => -1.0
1.0 => 1.22465e-16

## Functions and multiple dispatch

In [45]:
function f(x,y)
return y*cos(x), y*sin(x)
end
f(π/3, 2)

Out[45]:
(1.0000000000000002,1.7320508075688772)
In [46]:
function newtonstep(f::Array, Df::Array, x::Array)
println("using vector definition");
return x - Df\f
end

function newtonstep(f::Number, Df::Number, x::Number)
println("using scalar definition");
return x - f/Df
end

Out[46]:
newtonstep (generic function with 2 methods)
In [47]:
newtonstep([1,1], [2. -1.; -1. 2.], [0.,0.])

using vector definition

Out[47]:
2-element Array{Float64,1}:
-1.0
-1.0
In [48]:
newtonstep(1, 2., 0)

using scalar definition

Out[48]:
-0.5

Inline declaration and anonymous functions:

In [49]:
f(x) = x^2;
g = x->x^3;
f(2), g(2)

Out[49]:
(4,8)
In [50]:
h = x->(cos(x),sin(x));
h.([π, π/3, π/8])

Out[50]:
3-element Array{Tuple{Float64,Float64},1}:
(-1.0,1.22465e-16)
(0.5,0.866025)
(0.92388,0.382683)
In [51]:
map(t->t[1], [h(i*π/10) for i = 0:10])

Out[51]:
11-element Array{Float64,1}:
1.0
0.951057
0.809017
0.587785
0.309017
6.12323e-17
-0.309017
-0.587785
-0.809017
-0.951057
-1.0        

Names of functions that modify their arguments by convention end in !. For instance, push! adds an element to a data structure, whereas empty! removes all content.

In [52]:
SomeSet = Set([1, 2]);
empty!(SomeSet)

Out[52]:
Set{Int64}()

Note the following pitfall, which arises because array indexing with : creates copies:

In [53]:
function setone!(v)
fill!(v, 1.);
end
A = [2. 2.; 2. 2.];
setone!(A[:,1]);
A

Out[53]:
2×2 Array{Float64,2}:
2.0  2.0
2.0  2.0

To instead pass a reference to the memory of the underlying array, use view:

In [54]:
setone!(view(A,:,1));
A

Out[54]:
2×2 Array{Float64,2}:
1.0  2.0
1.0  2.0

## Custom types

In [55]:
abstract RoundShape

type Ellipse <: RoundShape
center::Tuple{Float64,Float64}
A::Real
B::Real
end

type Circle <: RoundShape
center::Tuple{Float64,Float64}
r::Real
end

getcenter(S::RoundShape) = S.center;

area(S::Ellipse) = π * S.A * S.B;
area(S::Circle) = π * S.r^2;

Ell = Ellipse((1., 0.), 2, 1);
getcenter(Ell), area(Ell)

Out[55]:
((1.0,0.0),6.283185307179586)

Note that concrete types (such as Ellipse, Circle) can only be subtypes of abstract types (here: RoundShape). Built-in abstract types are, e.g., Number, Real, Integer

## Macros

In [56]:
@time s = 0; for i=1:1e6 s += i^3; end; print("result: ", s);

  0.000004 seconds (130 allocations: 7.719 KB)
result: 2.5000050000024622e23
In [57]:
@time s = sum(map(x->x^3, 1:1e6)); print("result: ", s);

  0.063250 seconds (36.78 k allocations: 9.301 MB)
result: 2.5000050000025e23
In [58]:
@code_native sin(π/2)^2

	.section	__TEXT,__text,regular,pure_instructions
Filename: math.jl
pushq	%rbp
movq	%rsp, %rbp
Source line: 358
movslq	%edi, %rax
cmpq	%rdi, %rax
jne	L25
movabsq	$__powidf2, %rax popq %rbp jmpq *%rax L25: movabsq$jl_throw, %rax
movabsq	\$4632704664, %rdi       ## imm = 0x114217698
callq	*%rax
nop

In [59]:
@sprintf "iteration %d: error %e, step size %e" 139 2.34232131e-5 1.290137e-6

Out[59]:
"iteration 139: error 2.342321e-05, step size 1.290137e-06"

## Modules

Modules are used to encapsulate functions and types. Additional modules can be installed by the built-in package manager.

In [60]:
Pkg.add("FixedSizeArrays");

INFO: Nothing to be done
INFO: METADATA is out-of-date — you may not have the latest version of FixedSizeArrays
INFO: Use Pkg.update() to get the latest versions of your packages


They are loaded by using or import (where the latter puts identifiers into a separate directory)

In [61]:
using FixedSizeArrays


For using and import, julia searches for modules in the directories listed in LOAD_PATH. To also search for module files you have created in /some/directory, add the following line to the file ~/.juliarc.jl,

push!(LOAD_PATH,"/some/directory")

See also workflow tips for working with modules.

## Language interoperability

Julia offers interfaces to FORTRAN, C, C++, and Python. It is especially easy to use existing Python packages:

In [62]:
Pkg.add("PyPlot");
using PyPlot
x = linspace(1e-2,1,10000);
plot(x, sin(1./x));

INFO: Nothing to be done
INFO: METADATA is out-of-date — you may not have the latest version of PyPlot
INFO: Use Pkg.update() to get the latest versions of your packages

In [63]:
using SymPy
x, y = symbols("x, y", real=true);
diff(x^y*exp(y*exp(x*y)) - x*y + 1, y)

Out[63]:
-x + x^y*(x*y*exp(x*y) + exp(x*y))*exp(y*exp(x*y)) + x^y*exp(y*exp(x*y))*log(x)