Welcome to Julia!
Julia is a new language focused on technical and numerical computing, and similar to systems such as Octave (or MATLAB), IDL, SciLab, and Python+NumPy+SciPy.
Although Julia's current focus is technical computing, due to a well-considered design and an extensive mathematical library, Julia has also been used to write such things as web servers, and microframeworks, raytracers and a Quake rendering engine.
This tutorial is organized as follows:
Julia has been designed from the ground up for the needs of modern technical computing.
As a young, open-source language and community, there are nearly unlimited opportunities for contribution. The community strives to be welcoming to new contributors in any capacity. Some suggested areas to pursue contribution:
documentation: as with any open-source project, good documentation is critical for continued growth (and as such, contributions are deeply appreciated!)
packages: there are a huge number of "greenfield" packages just waiting to be written (or implemented in pure Julia). Come build something new, just the way you've always wanted it!
library: Because Julia's library is written in the language itself, the barrier-to-entry for contributions to the core repository is much lower than other projects written in a mix of languages. Pull requests are always welcome! Here is one list of potential areas to contribute.
core: the core of Julia is written in high-quality, terse, highly-readable C. Julia's code generation is written in C++ (with extern C
entry points) in order to interface with the LLVM JIT engine. There are many optimizations left to be written (for even better performance), and anyone with the skills and interest is welcome and encouraged to contribute.
#### The Julia Language project on GitHub: source code, issue tracker
#### julia-users mailing list usage questions and general discussions
#### julia-dev mailing list core language and standard library discussions (note: both mailing lists are open to public)
#### Packages and the package manager, fostering collaborative ecosystem development.
In [3]:
println("Hello, world!")
Simple enough, right? Now, let's make a function:
In [1]:
function sayhello(name)
println("Hello, ", name)
end
Out[1]:
...and call it:
In [2]:
sayhello("friend!")
The "Hello, world" example could be written on the command line as:
julia -e 'println("Hello, world")
Or as a script, by placing the command in hello.jl
and running:
> julia hello.jl
To write text without appending a newline, use print()
:
In [6]:
print(1)
print(" 2")
This tutorial can be run in IJulia, and viewed statically using the IPython notebook viewer (IJulia is built on the remarkable IPython software)
In IJulia, each In [#]
line denotes an individual cell containing one or more lines of Julia code, which may be executed by pressing Shift-Enter
. Program output or errors will be displayed immediately below the input cell, and the return value of the last command in the cell will be printed in Out[#]
box.
In [1]: println("Hello, world!")
Hello, world! <-- standard output is captured and printed sans Out[] prefix.
In [3]: "Hello, friend!"
Out[3]: "Hello, friend!" <-- expression values are printed in the Out[] cell
In [1]:
i = 2.0
Out[1]:
We can define complex variables using the special symbol im
denoting the imaginary unit:
In [2]:
e = 1+1im
Out[2]:
Julia contains a set of built-in constants, which may be used in computation or assigned to other variable names:
In [3]:
supercalifragilisticexpialidocious = pi
supercalifragilisticexpialidocious/2
Out[3]:
The cell above calculates pi/2
using the special variable supercalifragilisticexpialidocious that we have set as equal pi
, and prints the result to Out [ ]
.
You can even use names of built-in variables and functions, if you so choose. (This is usually not a good idea since it is very easy to write confusing code; Julia will warn you against overwriting built-ins, but will not stop you in this case)
You cannot, however, use the names of Julia keywords for your variable names.
In [13]:
end=0.5im
In [10]:
supercalifragilisticexpialidocious == pi
Out[10]:
In [3]:
pi == 3.2 # Sorry, Indiana
Out[3]:
In [5]:
pi < 3.2
Out[5]:
Inexact comparisons should use isapprox
In [16]:
isapprox(exp(im * pi) + 1, 0)
Out[16]:
In [17]:
'a' == char(97) # ASCII 97 is 'a'
Out[17]:
In [20]:
println("1\n2") #Newline
In [21]:
println("1\t2") #Tab
To print the baskslash character itself, it must be escaped with another backslash.
In [17]:
println("1\\2") #backslash
The dollar sign has special meaning in Julia and must be escaped to print correctly.
In [18]:
println("\$")
What happens if we don't escape it?
In [19]:
println("$pi")
The dollar sign performs string interpolation, splicing the value of the variable name after the $
into the output. Attempting to splice a non-existent variable results in an error:
In [20]:
println("$α")
Julia supports Unicode for string contents as well as variable names. So variables:
In [21]:
α = 7.29735257E-3
Out[21]:
and formulae:
In [22]:
z = 3+4im
ξ = 1/z
Out[22]:
May be written with any appropriate character.
Or even using non-Latin alphabets!
In [23]:
アルコール = 0.1337
アルコール^2
Out[23]:
On some operating systems and browsers, you may even be able to print pizza!:
In [24]:
print(char(0x1f355))
print(" = ")
print('\U263A')
In [25]:
re = r".*(brown fox).*(lazy dog)"
Out[25]:
In [26]:
m = match(re, "The quick brown fox jumped over the lazy dog.")
Out[26]:
In [27]:
m.captures[1]
Out[27]:
In [28]:
m.captures[2]
Out[28]:
In [29]:
if false
println("Twilight zone...")
elseif ( 1 == 2 )
println("We can actually do math, right?")
else
println("All is well. Both conditions are false.")
end
In [30]:
for i in 1:10
print(i, " ")
end
Breaking it down:
i in 1:10
returns an iterator over the range 1 to 10println
, we use simply print
, which does not add a newline after each outputWe can also skip elements using the continue
keyword:
In [31]:
for i in 1:10
(i > 5 && i < 9) && continue
print(i, " ")
end
In [ ]:
In [119]:
# opening a file:
vecfile = open("/tmp/myvector.txt", "w")
Out[119]:
Notice that the open
function returns a variable with the type IOStream. This is important, because the behavior of other functions will be influenced by the type of this variable.
Write a function that prints the comma-separated numbers from 1 to 5, in increments of .5, to the file handle vecfile
defined above.
print
function is overloaded, and will accept an IOStream as the first argument.
In [23]:
function writenumbers(fhandle)
# implementation goes here
end
Out[23]:
In [121]:
writenumbers(vecfile)
Be sure to clean up the file handle:
In [122]:
close(vecfile)
In [26]:
f_in = open("/tmp/myvector.txt", "r")
myvec1 = split( readall(f_in), "," )
Out[26]:
Since this is a CSV file, we could also use the built-in readcsv
function:
In [27]:
seekstart(f_in)
myvec1 = readcsv( f_in )
Out[27]:
We have used the built-in function seekstart
to go back to the beginning of the file, so that we don't have to open it again.
In [28]:
allmixedup = { 1, pi, "foo", myvec1, "biggles", sayhello }
Out[28]:
Square brackets, [ ]
, are used to retrieve the contents at a specific index.
Note: Julia uses 1-based indexing just like Fortran. Everything will be ok.
In [30]:
allmixedup[1]
Out[30]:
In [31]:
allmixedup[5]
Out[31]:
In [38]:
allmixedup[6]
Out[38]:
As demonstrated above, can store a reference to a function like any other object!
List elements can be set by assigning to an index:
In [126]:
push!(allmixedup, "another random string")
Out[126]:
In [32]:
In [41]:
mymap = { 1 => "one", "two" => 2, "sayhello" => sayhello, sayhello => "function called `sayhello`"}
Out[41]:
(mymap
above accepts any type of object, but as will be seen, dictionary key and value types may be restricted)
Key-value pairs may also be assigned individually:
In [42]:
mymap[7.3] = "seven point 3"
Out[42]:
Map indexing is similar to lists:
In [43]:
mymap[1]
Out[43]:
In [44]:
mymap["two"]
Out[44]:
In [45]:
mymap["sayhello"]
Out[45]:
In [46]:
mymap[sayhello]
Out[46]:
In [47]:
mymap[7.3]
Out[47]:
Numbers are the soul of Julia, so it is fitting that all numeric types are defined in the language itself. For example, the 64-bit integer is declared as follows (in src/base/base.jl, for the curious):
In [48]:
bitstype 64 Int64 <: Signed
This means that you can define your own data types in pure Julia, and expect the same performance profile as "core" types!
One goal of Julia is to be fast enough that moving to another language for performance should never be necessary.
We can write numeric literals in binary representation:
In [49]:
0b10 == 2 # There are 10 kinds of programmers...
Out[49]:
Julia is also quite willing to expose the when you want to see them. Binary representations are one example, and can be easily inspected:
In [50]:
[ 0 bits(0); 1 bits(1); 2 bits(2); 3 bits(3); 7 bits(7); 8 bits(8);
16 bits(16); 17 bits(17); 32 bits(32); 33 bits(33); 35 bits(35); 64 bits(64);]
Out[50]:
Literal values without a decimal point are interpreted as integers:
In [51]:
x = 1
Out[51]:
In [52]:
typeof(x)
Out[52]:
Julia has both signed and unsigned Integer types for 8, 16, 32, 64, and 128-bits.
Each Integer type can hold a specific and finite range of values:
In [33]:
[ typemin(Uint8) typemax(Uint8) ;
typemin(Int8) typemax(Int8) ;
typemin(Int64) typemax(Int64) ; ]
Out[33]:
Literals entered with a decimal point are intepreted as floating-point numbers:
In [54]:
y = 2.0
Out[54]:
In [55]:
[ 2. typeof(2.);
.5 typeof(.5); ]
Out[55]:
Note that 0 == -0
, but they do not have the same binary representation:
In [56]:
0 == -0
Out[56]:
In [57]:
[ " 0" bits(0.0) ;
"-0" bits(-0.0) ; ]
Out[57]:
Julia does arithmetic using machine numbers which can represent only finite ranges and with finite precision (as compared to the idealized $\mathbb{R}$eal numbers). The eps
function returns the smallest difference that can be represented by a given type:
In [34]:
[ 1.0 bits(1.0)
repr(1.0 + eps(1.0)) bits(1.0 + eps(Float64)) ]
Out[34]:
In [40]:
typemax(Int64)
Out[40]:
In [41]:
typemax(Int64) + 1
Out[41]:
For more information on numerical representations and accurate computation with machine math, please see the excellent discussion in the Julia manual (and links therein).
In [4]:
z=3+4im
Out[4]:
After defining a complex variable, addition and multiplication "just work":
In [5]:
z+z
Out[5]:
In [6]:
z*z' # z' denotes the conjugate of z (complex, of course)
Out[6]:
Operations on heterogenous types result in promotion to the most representative common type:
In [64]:
x + y
Out[64]:
In [65]:
typeof(x + y)
Out[65]:
In [66]:
0x1 + 2
Out[66]:
In [67]:
[ typeof(0x1) typeof(0x1 + 2) ]
Out[67]:
For the sake of completeness, here is a review of all of the standard arithmetic operators:
In [68]:
rtmc = [ 1+1 -2 1/2 2/3 3\2 2^3 x%y y^4 ]
Out[68]:
The rtmc
assignment above is an example of array declaration, with array elements computed in place using the specified inputs and operations.
Again, Julia converts all operations to the most representative type for given arguments. For example, division between integers results in a floating point number:
In [69]:
[ 2/3 typeof(2/3) ]
Out[69]:
This rule is applied consistently even if the result could be represented exactly by the common type of the input variables:
In [70]:
[ 4/2 typeof(4/2) ]
Out[70]:
In [71]:
a = ones(5)
Out[71]:
In [72]:
b = [ 5:9 ] # 5-9, inclusive
Out[72]:
In [73]:
c = eye(4) # returns the 4x4 identity matrix
Out[73]:
In [74]:
d = ones(4,4) # returns a 4x4 matrix of ones
Out[74]:
We can do arithmetic with array elements:
In [75]:
a[1] + b[1]
Out[75]:
And with entire arrays, such as addition:
In [76]:
a + b
Out[76]:
... scalar multiplication:
In [77]:
5 * b
Out[77]:
... and exponentiation:
In [78]:
b .^ 2
Out[78]:
This last one is important: the .^
operator denotes element-wise operation. As a general convention in Julia, element-wise operators are denoted with a dot prefixed to the operator symbol.
Julia has a powerful type system, and variables carry type information that is automatically inferred at the time of assignment (and helpfully printed by IJulia):
In [79]:
rtmc = [ 1 1+1 -2 1/2 2/3 3\2 2^3 x%y y^4 ]
Out[79]:
Although it is fully possible to program in Julia without explicit type declarations, the type system is fundamental to higher-level and generic programming. Let's make a quick review of some types encountered so far:
In [80]:
typeof('a')
Out[80]:
In [81]:
typeof("Quick brown fox")
Out[81]:
In [82]:
typeof(1)
Out[82]:
In [83]:
typeof(1.0)
Out[83]:
In [84]:
typeof([1.0 2.0 3.0])
Out[84]:
In [85]:
typeof([ 1 => "2", 2 => "2"])
Out[85]:
In [86]:
typeof(allmixedup)
Out[86]:
This last one is special: the Any
type is the root of the type hierarchy in Julia.
In [87]:
int_to_string = Dict{Int,String}()
Out[87]:
Or, we can ask Julia to select inferred types by using square bracket [ ]
comprehension instead of the curly bracket { }
comprehension used earlier:
In [88]:
int_to_string = [ 1 => "1", 2 => "2"]
Out[88]:
In [89]:
typeof(int_to_string)
Out[89]:
Assigning an Integer-String pair works:
In [90]:
int_to_string[3] = "foo"
Out[90]:
But, unsurprisingly, assigning a String-String pair does not:
In [91]:
int_to_string["item3"] = "baz"
These last lines failed becase int_to_string
expects only Integers as keys, and only strings as values.
In [92]:
type LP
c # Types are optional
A::Matrix{Float64}
b::Vector{Float64}
end
randlp(n,m) = LP(rand(n),rand(n,m),rand(m))
mylp = randlp(10,5)
println(mylp.c)
In [93]:
type LP2{T}
c::Vector{T}
A::Matrix{T}
b::Vector{T}
end
We can construct derived types by specifying the element type in the constructor, between { }
curly brackets following the type name:
In [94]:
lp = LP2{Float64}(mylp.c,mylp.A,mylp.b) # dbl precision
Out[94]:
Recall the IJulia output after the definition of sayhello
in the first section:
In [146]: function sayhello(name)
println("Hello, ", name)
end
Out[146]: sayhello (generic function with 1 method)
Notice the line (generic function with 1 method)
.
What happens if we define another version of this function with a different argument type?
In [95]:
sayhello(friendnumber::Number) = println("Hello, numerical friend ", friendnumber)
Out[95]:
Aha! No error. Instead, IJulia tells us that sayhello
now refers to a function with two methods. Compare the output when we call this function with different types of arguments:
In [96]:
sayhello("Bob")
In [97]:
sayhello(3)
In [98]:
sayhello(4.0)
When we call sayhello
with a String argument, the original definition with unspecified type is used. When we call sayhello
with any numerical type, the new Number-specific version is called.
We can make another, even more specific definition; compare the output now with different argument types:
In [99]:
sayhello(fpfriend::FloatingPoint) = println("Hello, floating point friend ", fpfriend)
Out[99]:
In [100]:
sayhello(4)
In [101]:
sayhello(5.0)
Why does this work? Julia's multiple dispatch chooses the most specific method for a given argument. Float64
is a subtype of Number
(twice removed), so the Float64
version is used for a floating point argument, but the earlier Number
version is still used for other numeric types.
Float64
is a subtype of the abstract FloatingPoint
type:
In [102]:
super(Float64)
Out[102]:
which is in turn a subtype of Number
:
In [103]:
issubtype(Float64, Number)
Out[103]:
In [26]:
function hierarchy(t::Type)
end
Out[26]:
In [27]:
hierarchy(Int64)
Expected output:
In[]: hierarchy(Int64)
Signed
Integer
Real
Number
Any
In [1]:
function array_sum(x::Array{Float64, 1})
y = 0
for i in 1:length(x)
y += x[i] + 2
end
return y
end
function array_sum(x::Array{Int64, 1})
y = 0
for i in 1:length(x)
y += x[i] + 1
end
return y
end
Out[1]:
In [2]:
array_sum([ 1, 2, 3, 4, 5 ]) # array of integers
Out[2]:
In [107]:
array_sum([ 1.0, 2, 3, 4, 5 ]) # array of floats
Out[107]:
Write a function that takes a vector and normalizes it in place by its L2 norm:
$\|x\|_2 = \sqrt{\sum\limits_{i=1}^n{|x_i|^2}}$
Hints:
sqrt
In [28]:
function vec_norm(ar::Array{Float64,1})
end
Out[28]:
Expected output
In [8]: vec_norm([1.0, 1.0, 1.0])
Out[8]:
3-element Array{Float64,1}:
0.57735
0.57735
0.57735
In [109]:
path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "PATH")
Out[109]:
In [110]:
bytestring(path)
Out[110]:
In [111]:
using PyPlot
x = -3pi:.01:3pi
plot(x, sin(x))
Out[111]:
Julia supports two kinds of code blocks. The first is delimited by:
In [112]:
begin
# and
"hello"
end
Out[112]:
And the second is delimted by parentheses, with expressions separated by a semi-colon ;
between each expression:
In [113]:
expression = 1
( this = 1; is = 2; println(a[1]); valid = expression )
Out[113]:
The final expression in a code block determines the resulting value, if any. Code blocking style can be used to great effect
In [114]:
function plus(x::Float64, y::Float64)
return x + y
end
Out[114]:
In [115]:
code_typed(plus, (Float64, Float64))
Out[115]:
In [116]:
code_llvm(plus, (Float64, Float64))
In [117]:
code_native(plus, (Float64, Float64))