A short tour of Julia syntax and features

Functions in Julia

The only way Julia can utalize the jit compiler is when code is written in functions. Therefore, much of the Julia code one writes will be within functions. Here is the basic syntax for defining functions.


In [1]:
function foo(x, y)
    w = x + y
    z = sin(2 * w)
    w ./ z, cos(w) # last line is what gets returned, multiple return values separated by comma, could use return
end


Out[1]:
foo (generic function with 1 method)

Note: the last line is what gets returned (you can add the keywork return at the end if you want but it is unnecessary). Also note that commas on the last line is used for multiple outputs.


In [2]:
a, b = foo(5.0, 7.0) # functions called with parenthesis
a


Out[2]:
-13.25120001035562

In [3]:
a, b = foo(ones(2,2), rand(2,2))
b


Out[3]:
2x2 Array{Float64,2}:
 -0.275748  0.386611
  0.469896  0.388211

If you have a short function, you can use one line syntax


In [4]:
foo2(a, b) = 1 + 2a + 5b^4 # Notice that `2a` gets parsed as `2*a`


Out[4]:
foo2 (generic function with 1 method)

You can also "pipe" functions.


In [5]:
a = sin(cos(exp(5)))
b = 5 |> exp |> cos |> sin
a == b


Out[5]:
true

Anonymous (i.e. un-named or lambda) functions

These are convient for piping and map. Note, in Julia 0.4 un-named functions are slow since they do not get jit compiled. This is changed in Julia 0.5 so un-named functions are fast.


In [6]:
x -> sin(x^2)


Out[6]:
(anonymous function)

In [7]:
y = 1.9 |> cos  |> z -> foo2(z,10) |> log


Out[7]:
10.819785352802628

In [8]:
map(x -> cos(x^2), [π/4,π/2,π])


Out[8]:
3-element Array{Float64,1}:
  0.815705
 -0.781212
 -0.902685

Function pass by reference.

Julia uses pass by reference so function can mutate it's arguments. This is nice for memory management. The convention is that these mutating functions have a ! at the end of the name


In [9]:
function foo!(x)
    x[:] = zeros(size(x))
	return nothing # optional
end


Out[9]:
foo! (generic function with 1 method)

In [10]:
a = [1 0; 0 1]
foo!(a)
a


Out[10]:
2x2 Array{Int64,2}:
 0  0
 0  0

Notice, however, that the following code doesn't mutate x since the line x = zeros(size(x)) rebinds local name x.


In [11]:
function foo!(x)
    x = zeros(size(x))
    return nothing # optional
end
a = [1 0; 0 1]
foo!(a)
a


Out[11]:
2x2 Array{Int64,2}:
 1  0
 0  1

Optional arguments, named arguments, variable length arguments

Optional arguments


In [12]:
function fop(x, base = 10)
    x = x^2
    return base * x
    x # this is ignored
end


Out[12]:
fop (generic function with 2 methods)

In [13]:
fop(1)


Out[13]:
10

In [14]:
fop(1, 2)


Out[14]:
2

Named arguments with semicolon


In [15]:
function tot(x, y; style=0, width=1, color=3)
    x + y + style + width/color
end


Out[15]:
tot (generic function with 1 method)

In [16]:
tot(1,2)


Out[16]:
3.3333333333333335

In [17]:
tot(1,2, width = 3)


Out[17]:
4.0

In [18]:
tot(1, 2; width = 3) # the semicolon in this case is un-necssary


Out[18]:
4.0

Variable length arguments


In [19]:
bez(a, b, c...) = println("$c")
bez(2,3, 4, 5, 6)


(4,5,6)

Splatting

Here is the splatting argument.


In [20]:
args = [4, 5]
fop(args...) # calls fob(args[1], args[2])


Out[20]:
80

Splatting works nicely with variable length arguments and dictionaries.


In [21]:
bez2(c...) = bez(1, 2, c...) # the second c is a spat, the first makes a variable length arg
bez2(4,5,6,7,8)


(4,5,6,7,8)

Splatting a dic for named arguments


In [22]:
dargs = Dict(:style => 5, :width => 3, :color => 1) # :style is of symbol type
tot(4, 5; dargs...) # now the ; is required


Out[22]:
17.0

Closures


In [23]:
function clos(data)
    # withing the function scope, data acts like a global variable
    function loglike(μ)
        -0.5 * sumabs2(data .- μ)
    end
    function updatedata(val)
        push!(data, val)
    end
    loglike, updatedata # return the two functions
end


Out[23]:
clos (generic function with 1 method)

In [24]:
like1, updatedata1 = clos(rand(10))


Out[24]:
(loglike,updatedata)

Now the data is closed off to any mutations other than those given by updatedata.


In [25]:
using PyPlot
[like1(μ) for μ=0.1:.1:3] |> x -> plot(0.1:.1:3, x)


Out[25]:
1-element Array{Any,1}:
 PyObject <matplotlib.lines.Line2D object at 0x31bd14910>

In [26]:
updatedata1(10) # add 10 to the data set


Out[26]:
11-element Array{Float64,1}:
  0.785204
  0.874436
  0.590801
  0.547854
  0.454559
  0.912119
  0.87392 
  0.622417
  0.357671
  0.155745
 10.0     

In [27]:
[like1(μ) for μ=0.1:.1:3] |> x -> plot(0.1:.1:3, x)


Out[27]:
1-element Array{Any,1}:
 PyObject <matplotlib.lines.Line2D object at 0x31bf63850>

For loops

Since for loops in Julia are fast, you often find yourself allocating arrays and filling the entries with a loop.


In [28]:
A = Array(Int64, (10,10)) # initialized 10x10 array of Int64's
for i =  1:10
    for j = 1:10
        A[j, i] = i + j
    end
end
A


Out[28]:
10x10 Array{Int64,2}:
  2   3   4   5   6   7   8   9  10  11
  3   4   5   6   7   8   9  10  11  12
  4   5   6   7   8   9  10  11  12  13
  5   6   7   8   9  10  11  12  13  14
  6   7   8   9  10  11  12  13  14  15
  7   8   9  10  11  12  13  14  15  16
  8   9  10  11  12  13  14  15  16  17
  9  10  11  12  13  14  15  16  17  18
 10  11  12  13  14  15  16  17  18  19
 11  12  13  14  15  16  17  18  19  20

You can equivalently use the following more terse syntax for nested loops.


In [29]:
A = Array(Int64, (10,10)) # initialized 10x10 array of Int64's
for i =  1:10, j = 1:10
    A[j, i] = i + j
end
A


Out[29]:
10x10 Array{Int64,2}:
  2   3   4   5   6   7   8   9  10  11
  3   4   5   6   7   8   9  10  11  12
  4   5   6   7   8   9  10  11  12  13
  5   6   7   8   9  10  11  12  13  14
  6   7   8   9  10  11  12  13  14  15
  7   8   9  10  11  12  13  14  15  16
  8   9  10  11  12  13  14  15  16  17
  9  10  11  12  13  14  15  16  17  18
 10  11  12  13  14  15  16  17  18  19
 11  12  13  14  15  16  17  18  19  20

Note: Julia stores it's arrays in column major order so it is fastest to order the for loop so the rows are the inner-most loop. This avoids the pointer from jumping around the memory layout.

In cases where you don't need the explicit row and column index you can use the iterator eachindex which automatically sets up the loop in the correct order. Here is an example.


In [30]:
A = zeros(1_000, 1_000)
B = rand(1_000, 1_000)
for i in eachindex(A,B)
    A[i] = sin(B[i])
end
A


Out[30]:
1000x1000 Array{Float64,2}:
 0.17991    0.229218    0.275771   …  0.459706   0.830693    0.385598 
 0.510489   0.614876    0.468954      0.181056   0.779598    0.699992 
 0.732463   0.409729    0.322478      0.147585   0.422132    0.40637  
 0.113541   0.364177    0.691102      0.350314   0.21991     0.744934 
 0.152542   0.789975    0.413444      0.712785   0.777708    0.373779 
 0.461411   0.356187    0.45029    …  0.212068   0.158405    0.0749313
 0.723602   0.513498    0.743754      0.0465685  0.41318     0.491846 
 0.822826   0.676046    0.626727      0.432881   0.367392    0.502083 
 0.373334   0.675792    0.793678      0.226752   0.665949    0.552286 
 0.549129   0.0600364   0.493749      0.839471   0.531292    0.512362 
 0.53716    0.679206    0.488475   …  0.78554    0.773549    0.515107 
 0.631374   0.131194    0.604041      0.437744   0.727886    0.106802 
 0.73909    0.182909    0.772168      0.534066   0.573683    0.378632 
 ⋮                                 ⋱                                  
 0.419067   0.374653    0.148909      0.667539   0.384061    0.460103 
 0.701085   0.668728    0.361668      0.252914   0.575963    0.10022  
 0.280532   0.502602    0.816667   …  0.546628   0.475389    0.546798 
 0.573289   0.0287218   0.315739      0.659834   0.144722    0.0411154
 0.516865   0.358198    0.031502      0.83552    0.460792    0.55962  
 0.773612   0.32225     0.214211      0.756119   0.0596437   0.588311 
 0.0296888  0.113704    0.637373      0.511445   0.394031    0.803816 
 0.0372335  0.00861837  0.151836   …  0.562863   0.555328    0.576511 
 0.308208   0.444523    0.108699      0.0437358  0.691215    0.684313 
 0.492223   0.320081    0.234176      0.395471   0.823839    0.432196 
 0.283526   0.719793    0.23195       0.0434574  0.77313     0.598442 
 0.261351   0.104836    0.0704513     0.812763   0.00979968  0.401741 

Control Flow

Here is the basic if,else conditional.


In [31]:
x = 2
if x == 5
	print(1)
else
	print(2)
end


2

There is also a nice way to do a quick if, else variable assignment


In [32]:
x = 2
y = x < 2 ? x : 0.0
y


Out[32]:
0.0

When using this operation in a function, be sure to make it type stable


In [33]:
x = 2
y = x < 2 ? x : zero(x)
y


Out[33]:
0

In [34]:
x = 2.0
y = x < 2 ? x : zero(x)
y


Out[34]:
0.0

These can also be chained.


In [35]:
x = 4.0
y = x < 2 ? 2 :
    x < 3 ? 3 :
    x < 4 ? 4 : Inf
y


Out[35]:
Inf

Julia also has comprehensions for quick construction of Arrays


In [36]:
[sin(x) for x = 1:3]


Out[36]:
3-element Array{Float64,1}:
 0.841471
 0.909297
 0.14112 

In [37]:
[abs(i - j) for i = [4, 7, 8], j = 1:3]


Out[37]:
3x3 Array{Int64,2}:
 3  2  1
 6  5  4
 7  6  5

It is generally a good idea to prefix the types of the elements of a comprehension


In [38]:
Float64[abs(i - j) for i = [4, 7, 8], j = 1:3]


Out[38]:
3x3 Array{Float64,2}:
 3.0  2.0  1.0
 6.0  5.0  4.0
 7.0  6.0  5.0

In [39]:
Array{Float64,1}[rand(i) for i in [1,2,3]]


Out[39]:
3-element Array{Array{Float64,1},1}:
 [0.559596719050496]                                          
 [0.4722313678257788,0.34067893539980854]                     
 [0.11096371043592579,0.01857446958823461,0.01744857828539015]

Fun extras

Easiy multiple assignment


In [40]:
a, b, c = 1, 2, 3


Out[40]:
(1,2,3)

Indexing arrays can happen at the end


In [41]:
a = (rand(10, 10) * rand(10, 10))[2, 2]


Out[41]:
3.146570357721367

String interpolation is easy


In [42]:
a = 5
"The variable a is assigned the value $a."


Out[42]:
"The variable a is assigned the value 5."

In [43]:
a = "ethan"
"The variable a is assigned the value $a."


Out[43]:
"The variable a is assigned the value ethan."

In [44]:
for a in ["baz", "boo", "bar"] # loop over a vector of strings
	println("The variable a is assigned the value $a")
end


The variable a is assigned the value baz
The variable a is assigned the value boo
The variable a is assigned the value bar

Dictionaries


In [45]:
a = Dict("bar" => 10, "baz" => 5, "boo" =>1)


Out[45]:
Dict{ASCIIString,Int64} with 3 entries:
  "bar" => 10
  "boo" => 1
  "baz" => 5

In [46]:
a["baz"]


Out[46]:
5

Immutable tuples


In [47]:
tul = (4, 5, 6)
tul[2]


Out[47]:
5

Immutable means the entries can not be mutated


In [48]:
tul[2] = 7


LoadError: MethodError: `setindex!` has no method matching setindex!(::Tuple{Int64,Int64,Int64}, ::Int64, ::Int64)
while loading In[48], in expression starting on line 1

Sets


In [49]:
As= Set([2,3,4,4,5,6])
Bs = Set([2,3,4])
Bs  As


Out[49]:
Set([4,2,3])

In [50]:
Bs  As


Out[50]:
Set([4,2,3,5,6])

Parallel computing

Parallel computing is fully distributed (i.e. not on shared memory) out of the box so you can immediatly perform calculations spread accross multiple computers. This also means you need to explicity send definitions to workers.

# Define the workers on multiple servers:
# 5 on the server hilbert and 5 on the server gumbel. 
jobs = [
    "hilbert",
    "hilbert",
    "hilbert",
    "hilbert",
    "hilbert",
    "gumbel",
    "gumbel",
    "gumbel",
    "gumbel",
    "gumbel"
]

# now launch the workers
addprocs(jobs)

# we need to define `foo` and `parameter` on each worker
@everywhere function foo(x)
    sum(x.^2)
end
@everywhere const parameter = 3.3

# now send out 1000 jobs (which will be automatically distributed to our 5 workers).
# Each job returns foo(x) to the main node which are combined by vcat.
out = @parallel (vcat) for r in 1:1_000
    x = rand(100)
    foo(x)
end

# close all workers (except for the main node)
closeprocs()

Shell scripting

System commands are another data type in Julia which can programmed on.

for ν in [0.8, 1.2, 2.2], ρ in [0.05, 0.2], xmax in [1.2, 1.4]
    run(`julia scripts/script1d/script1d.jl    $prdc_sim $xmax`)
end

In [ ]: