Note that some terminologies may be slightly different from those from the Julia Documentation. It depends on my understanding and my opinions about what is a better mnemonic.
- Introduction
- Installation
- A quick tour of Julia
- Enter and quit Julia interactive session
- Something special to the REPL
- Get help in REPL
- Play with Julia script file and command line
- Run shell command in Julia
- Comment code
- Variables
- Built-in numeric primitives
- Array
- String
- Regular Expressions
- Elementary mathematical operations and functions
- Support for complex and rational numbers
- Control flow
- Compound expressions
- Function
- Special ellipsis "..." symbol
- Variable scope
- Operators
- Types
- Methods
- Constructors
- Tasks
- Exception handling
- Write your own package
- Some interesting facts
- Useful resources
Julia is a fast dynamic programming language for technical computing. With modern language design and compiler techniques, Julia aims to create an unprecedented combination of easy-to-use, power, and efficiency in a single language, providing ease and expressiveness in the same way as languages such as R and Python, and also good performance like C.
More specific details can be found at Julia Downloads and Platform Specific Instructions for Installing Julia.
Unfortunately, there is no Julia PPA for you to sudo apt install
,
though there was a Julia PPA (Personal Package
Archives).
Now you need to download the "64-bit Generic Linux Binaries for x86"
like
julia-1.0.1-linux-x86_64.tar.gz
,
untar it, then make a symbolic link in /usr/local/bin/
to
bin/julia
in the untared folder like julia-1.0.1
. (Note: In
this tutorial, we use "$" like below as the prompt for shell
script.)
$ wget https://julialang-s3.julialang.org/bin/linux/x64/1.0/julia-1.0.1-linux-x86_64.tar.gz
$ tar xf julia-1.0.1-linux-x86_64.tar.gz
$ sudo ln -s ./julia-1.0.1/bin/julia /usr/local/bin/julia
If you want to use an IDE for programming with Julia, see Juno, A flexible IDE for the 21st century for more details.
Download the .dmg
file such as
julia-1.0.1-mac64.dmg
on Julia Downloads and install it.
Download the .exe
file such as
julia-1.0.1-win64.exe
on Julia Downloads and install it.
Open a terminal and type:
$ julia
to get into the interactive session of Julia, within which you type: (Note: In this tutorial, we use "julia>" like below as the prompt for Julia)
julia> exit()
or Ctrl-d to quit back to shell.
-
Once you have typed a complete expression, pressing Enter will get the expression be evaluated and the value will be printed out. A trailing semicolon
;
will suppress the output for the evaluated value.julia> 1+2 3 julia> 1+2; julia>
-
However, you can use the variable
ans
, which is only available in REPL, to get the value of the last evaluated expression, no matter if it is suppressed or not.julia> 1+2; julia> ans 3
-
You can type
julia -q
to suppress the display of startup banner (like below) when lauching the REPL._ _ _ _(_)_ | A fresh approach to technical computing (_) | (_) (_) | Documentation: http://docs.julialang.org _ _ _| |_ __ _ | Type "help()" to help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 0.3.5 (2015-01-08 22:33 UTC) _/ |\__'_|_|_|\__'_| | Official http://julialang.org release |__/ | x86_64-linux-gnu
-
For more about command julia itself, just type:
$ julia --help
With the help()
function, one can query the documentation for a
specific function, macro, or variable. For example,
julia> help(println)
Alternatively, we can use ?
as a short hand for the help()
function:
julia> ?println
If you are not sure how to use help()
, just type:
julia> ?help
for more details.
apropos()
is a more flexible function that can be used to search
documentation for functions related to a specific string. For
example, if one want to find out what functions there are to do with
"string", one can type:
julia> apropos("string")
And the blog Julia Helps gives a good summary about getting help from Julia itself.
A Julia script file has .jl as its extension.
-
To execute the code in a Julia script file within a Julia interactive session, we use function
include()
. For example, if we have a script named hello.jl with the following contents:# hello.jl println("Hello, world!")
then in a Julia session, type:
julia> include("hello.jl") Hello, world!
-
To execute a Julia script in shell, for example, previous hello.jl, type:
$ julia hello.jl
-
To execute a short code within shell command line, we could use the option
-e
for commandjulia
:$ julia -e 'println("Hello, world!")'
-
To execute a Julia script in shell with one or more command line arguments, Julia provides
ARGS
for holding the arguments passed from command line. For example, we have a script named hello2.jl:# hello2.jl println("Hello, ", ARGS[1], "!")
then in shell, type:
$ julia hello2.jl Simon Hello, Simon!
-
Or one can also provide command line arguments for short code expressions:
$ julia -e 'println("Hello, ", ARGS[1], "!")' Simon Hello, Simon!
which has the same output as above.
A shell command should be wrapped in backticks "****", and we can use function
run()` to run the command within them. For example,
julia> run(`echo Hello`)
Hello
However, the output will automatically be dumped to the screen. We
can assign the output to a variable by function readall()
:
julia> a = readall(`echo Hello`)
"Hello\n"
julia> a
"Hello\n"
-
In a single Julia script line, everything behind a
#
is considered as comment. -
#=
and=#
are used for multi-line comments.
Like other languages, variable names in Julia must also begin with a letter in English alphabet, underscore, or a subset of Unicode code points greater than 00A0.
As a dynamic programming language, there is no restriction for a variable in Julia to be of a fixed type after introduced in a scope. In other words, a variable can be assigned to a value of different type from its previous one, except that in a local scope we can add a type annotation to a variable to restrain it from being assigned to a value of a different type.
Note: When in the REPL, we are in a global scope, and a variable defined in a function is in a local scope.
Examples:
x = 1 # introduce a varialbe named x
x += 1 # now x equals to 2
x = "Hello" # x is re-assigned to a string "Hello"
function foo()
x::Int32 = 10 # prevent x from being assigned to values of non-Int32
end
For expressiveness, one can use Unicode names (in UTF-8 encoding) as variable names, which, I think, is good for math symbols. One can enter these math symbols by typing the backslash LaTeX symbol name followed by Tab. For example, δ can be entered by typing "\delta" and followed by Tab key.
julia> δ = 0.125
0.125
julia> δ
0.125
Variable | Description | Example |
---|---|---|
WORD_SIZE |
Indicate whether the target system is 32-bit or 64-bit |
For basic arithmetic, Julia provides a series of built-in numeric types, with explicit names telling how many bits they use to represent a number:
# Unsigned integers can be represented by 0x followed by hexadecimal
# digits, or 0b followed by binary digits, or 0o followed by octal
# digits, such as 26 can be represented as 0x1a, 0b11010, or 0o32.
Int8 Uint8
Int16 Uint16
Int32 Uint32
Int64 Uint64
Int128 Uint128
Bool # false or true with 8 bits
Char # Unicode characters with 32 bits, denoted by enclosing printable
# characters in single quotes, such as 'x', or escaped '\u' or
# '\U' hexadecimal input forms, such as '\u78', which is the
# same as 'x'.
# There is no type named Double as in other programming languages
Float64 # Floating-point numbers are by default of type Float64, such as 25, 2.5e1
Float32 # numbers of type Float32 can be represented by writing an 'f' in place of 'e', such as 2.5f1
Float16
A few useful functions that can work with these basic built-in types:
Function | Description | Example |
---|---|---|
typeof(x) |
The exact concrete type of x |
typeof(10.04) returns Float64 |
typemin(x) |
The lowest value representable by the given numeric type | typemin(Int32) returns -2147483648 |
typemax(x) |
The highest value representable by the given numeric type | typemax(Int32) returns 2147483647 |
bits(x) |
A string giving the literal bit representation of a number | bits(0x7) returns "00000111" |
num2hex(x) |
A hexadecimal string of the binary representation of a floating point number | num2hex(1.0) returns "3ff0000000000000" |
bin |
Convert an integer to a binary string | bin(10) returns "1010" |
oct |
Convert an integer to an octal string | oct(10) returns "12" |
hex |
Convert an integer to a hexadecimal string | hex(10) returns "a" |
dec |
Convert an integer to a decimal string | dec(0xa) returns "10" |
eps(x) |
The distance between x and the next larger representable floating-point value of the same type as x |
eps(Float32) returns 1.1920929f-7 |
And there are a same number of functions with the same names but in
lower case for converting a value to corresponding numeric types. For
example, uint64(x)
converts x
to Uint64
data type, char(120)
converts the integer value 120
to a Char
, which turns out to be
'x'
.
In addition, a set of special floating-point values is provided, such
as Inf
, -Inf
, NaN
. More details can be found at
Julia Manual - Integers and Floating-Point Numbers
An array in Julia is indexed from 1, not 0. And the last element can
be accessed by using index end
. Range indexing can be done by using
:
, such as 1:5
(range [1, 2, 3, 4, 5]
) or 2:2:10
(range
[2, 4, 6, 8, 10]
)
Note the difference between arrays constructed with and without comma:
julia> x = [1, 2, 3]
3-element Array{Int64,1}:
1
2
3
julia> x = [1 2 3]
1x3 Array{Int64,2}:
1 2 3
String literals are denoted by being enclosed in double quotes, or
triple double quotes when a string contains double quotes, since
escape the quotes with \
would be less readable:
julia> a = "Hello";
julia> b = """Escape the quotes with "\\" would be less readable""";
julia> c = "Escape the quotes with \"\\\" would be less readable";
julia> b == c
true
Strings can be constructed by function string
or string
interpolation with $
:
julia> "1 + 2 = $(1+2)"
"1 + 2 = 3"
julia> string("1 + 2 = ", 1+2)
"1 + 2 = 3"
Here $
can be followed by any expressions enclosed in parentheses.
Indexing a specific character in a string is like in an array, the first character is also at index 1.
julia> a = "Hello"
"Hello"
julia> a[1]
'H'
julia> a[1:3]
"Hel"
However, when the characters in a string aren't in ASCII, the default encoding of Julia --- UTF-8 --- allows those characters represented with multiple bytes, so that indexing a string may not get a valid character.
julia> a = "∀x ∃y"
"∀x ∃y"
julia> s = "\u2200x \u2203y"
"∀x ∃y"
julia> a == s
true
julia> s[1]
'∀'
julia> s[2]
ERROR: invalid UTF-8 character index
in getindex at utf8.jl:63
julia> s[3]
ERROR: invalid UTF-8 character index
in getindex at utf8.jl:63
julia> s[4]
'x'
So the common and effective way to iterate through the characters in a string is:
julia> for c in s
println(c)
end
∀
x
∃
y
Here are a few useful function related to strings (We can also find
more by typing apropos("string")
in REPL):
Function | Description | Example |
---|---|---|
length(s) |
The number of characters in string s |
length("∀x ∃y") returns 5 |
sizeof(s) |
The number of bytes in string s |
sizeof("∀x ∃y") returns 9 |
endof(s) |
The index of the last character of s |
endof("∀x ∃") returns 6 |
chr2ind(s, i) |
The index of the i -th character of s |
chr2ind("∀x ∃y", 4) returns 6 |
nextind(s, i) |
The next valid string index after i of the string s |
nextind("∀x ∃y", 1) returns 4 for the very index of character x |
prevind(s, i) |
The previous valid string index before i of the string s |
prevind("∀x ∃y", 3) returns 1 |
lowercase(s) |
A string of lowercase of s |
lowercase("Hello") returns "hello" |
uppercase(s) |
A string of uppercase of s |
uppercase("Hello") returns "HELLO" |
strip(s) |
A string of s but with any leading and trailing whitespace removed |
strip(" Hello ") returns "Hello" |
searchindex(s, sub) |
The start index at which the substring sub is found in s |
searchindex("Hello", "e") returns 2 |
beginswith(s, prefix) |
Whether string s begins with prefix |
beginswith("music001", "music") returns true |
join(s, d) |
Join an array of strings s into a single string, inserting the given delimiter d between adjacent strings |
join(["Hello", "world"], ", ") returns "Hello, world" |
repeat(s, i) |
Construct a string of i -times repeated concatenation of s |
repeat("@#", 3) returns "@#@#@#" |
replace(s) |
Julia's regular expression is Perl-compatible. We can construct a
pattern by prefixing a pattern string with r
. We will give a short
example to explain its usage (which is from
Julia manual - Strings):
julia> m = match(r"(a|b)(c)?(d)", "ad")
RegexMatch("ad", 1="a", 2=nothing, 3="d")
julia> m.match
"ad"
julia> m.captures
3-element Array{Union(Nothing,SubString{UTF8String}),1}:
"a"
nothing
"d"
julia> m.offset
1
julia> m.offsets
3-element Array{Int64,1}:
1
0
2
julia> first, second = m.captures; first
"a"
The basic arthmetic and bitwise operations are similar to other programming languages, such as C:
+ - */
\ # inverse divide, e.g., x\y is equivalent to y/x
^ # power, e.g., 2^3 equals to 8
% # remainder, e.g., 3%2 equals to 1
! # negation
~ & | $ # bitwise not, and, or, xor
>>> # logical shift right
>> # arithmetic shift right
<< # logical/arithmetic shift left
# corresponding updating operators
+= -= *= /= %= ^= &= |= $= >>>= >>= <<=
# comparisons
==
<
<= ≤ # Can be entered by typing "\le" followed by Tab
>
>= ≥ # Can be entered by typing "\ge" followed by Tab
!= ≠ # Can be entered by typing "\ne" followed by Tab
Special note should be taken when comparing Inf
, NaN
, see
Julia Manual - Mathematical Operations and Elementary Functions
Julia allows chained comparisons, which can be re-written by &&
:
1 < 2 <= 3 > 0 # equals to: 1 < 2 && 2 <= 3 && 3 > 0
A list of useful functions frequently used in mathematics can be found at Julia Manual - Mathematical Operations and Elementary Functions
Complex and rational number are pre-defined types in Julia.
-1 + 2im # a complex number, "im" denotes imaginary part
3//4 # a rational number equals to 0.75
Corresponding arithmetic operations are also defined for complex and rational numbers.
(-1 + 2im)*(-1 - 2im) # 5 + 0im
3//4 * 1//3 # 1//4
Conditional evaluation can be obtained by the if-elseif-else-end
construct. For example,
x = 1
if x > 0
y = x * 2
elseif x < 0
y = -x * 2
else
y = 1
end
Note that elseif
and else
are optional, and the conditions
must be expressions that can be evaluated to be boolean values such as
true
and false
.
while...end
loops are like:
i = 1
while i <= 5
println(i)
i += 1
end
And the for...end
loop do the same thing is like:
for i = 1:5
println(i)
end
which is equivalent to
for i in 1:5
println(i)
end
break
and continue
are also the same in Julia as the other
programming languages such as C.
The special usage of for
loops below:
for i = 1:3, j in 1:5
println(i, ", " , j)
end
is a syntactic sugar of:
for i = 1:3
for j = 1:5
println(i, ", " , j)
end
end
However, a break
will terminate the entire loop that use the
concise nested form.
In Julia, we can use begin...end
or (;)
chains to form a single
compound expression out of several sub-expressions. In other words,
it can be considered as a small one-time-use function with the value
of the last sub-expression as its return value.
The four ways we can write a compound expression are (The examples below are borrowed from Julia manual - Compound Expressions.):
-
begin...end
in a single line:z = begin x = 1; y = 2; x + y end
-
begin...end
cross multiple lines:z = begin x = 1 y = 2 x + y end
-
(;)
in a single line:z = (x = 1; y = 2; x + y)
-
(;)
cross multiple lines:z = ( x = 1; y = 2; x + y )
Note that when in multiple lines, ";
" should be kept in (;)
chains, while in begin...end
, it can be droped. (? I am not
sure whether there is other usage of (;)
, but here it seems
there is no big difference between begin...end
and (;)
,
except that (;)
saves typing.)
- maybe need to be more concise like Compound expressions
Functions are defined with function...end
:
function greaterThan(x, y)
return x > y
end
Here we define a function which we can refer it by the name
greaterThan
. And the return
in the function is optional,
which means it can be written as:
function greaterThan(x, y)
x > y
end
This is because that in Julia, the value of the last evaluated
expression in the body of a function will be considered as the return
value of that function by default, unless some other value is
explictly indicated as return value by return
somewhere before the
last expression.
In Julia, function is treated the same as any other object. That is, they can be assigned to variables, used as arguments to other functions, and returned as values, besides called as functions with the parenthesis form. All we need is something we can refer to it, such as a name.
We can call a function with its name followed by the actual arugment
enclosed in parentheses, or by the apply
function:
a = 1
b = 2
if greaterThan(a, b)
println("a > b")
else
println("a <= b")
end
which is equivalent to:
a = 1
b = 2
if apply(greaterThan, a, b)
println("a > b")
else
println("a <= b")
end
The apply
function takes another function as its first argument,
and then apply that function to the remain arguments of itself.
If the body of a function consists of only one single expression, the function can also be defined like:
greaterThan(x, y) = return x > y
or equivalently:
greaterThan(x, y) = x > y
or even:
greaterThan = (x, y) -> x > y
which is defined by assigning an anonymous function to the
variable greaterThan
.
We can also pass an anonymous function to another function that take
functions as its arguments, for example with the previous apply
function:
apply((x, y) -> begin
if x > y
println(x, " > ", y)
else
println(x, " <= ", y)
end
end,
1,
2)
which can be written in a more compact way as:
apply(1, 2) do x, y
if x > y
println(x, " > ", y)
else
println(x, " <= ", y)
end
end
The do...end
is another syntactic sugar that creates an anonymous
function with arguments afterwards (here are x
and y
), to be
passed as the first argument to the function before it (here is the
apply
function), where the rest arguments will be the second, the
third one and so on.
In Julia, multiple values are returned by being wrapped in a tuple (A tuple is an ordered list of values grouped by parentheses with each value seperated from eath other by comma, ). However, a syntactic sugar is provided to allow tuples being created and destructured without needing parentheses:
julia> foo(a, b) = (a+b, a*b);
julia> foo(2, 3)
(5,6)
julia> foo(a, b) = a+b, a*b;
julia> foo(2, 3)
(5,6)
julia> x, y = foo(2, 3)
(5,6)
julia> x
5
julia> y
6
Optional arguments are arguments with default values, so that functions with optional arguments can be invoked without given explicit values for those optional arguments if the default values are appropriate enough.
Optional posisional arguments are optional arguments. The word "positional" means that in a call, they should be passed in the same order specified in the function definition.
julia> foo(x, y=1, z=1) = x + y + z
foo (generic function with 3 methods)
julia> methods(foo)
# 3 methods for generic function "foo":
foo(x) at none:1
foo(x,y) at none:1
foo(x,y,z) at none:1
julia> foo(1)
3
julia> foo(1, 2)
4
julia> foo(1, 2, 3)
6
julia> foo(1, , 3)
ERROR: syntax: unexpected ,
The last results above tells if one specifies a value for a optional positional argument in a call, the values of all prior optinal positional arguments must also be explicitly specified.
Therefore, keyword arguments come to rescue. Keyword arguments are optional arguments too. Different from positional arguments, keyword arguments can be referred by the label (keyword) attached to them. Thus, in a call, they can be placed in any order, but must be with explicitly reference by the labels, no matter what order they are in. Keyword arguments sequences are defined and seperated from other type of arguments by using a semicolon in the signature of the function.
julia> foo(x, y=1, z=2; u=3, v=4) = x - (y + z) + u * v;
julia> foo(1;)
10
julia> foo(1)
10
julia> foo(1, 2, 3; u=5, v=6)
26
julia> foo(1, 2, 3, u=5, v=6)
26
julia> foo(u=5, 1, v=6, 2, 3)
26
The last results above tells keyword arguments can be mixed with other arguments in a call.
Both optional positional and keyword arguments (which are collectively called optional arguments) should have default values. Subsequent optional arguments may refer to prior arguments in defining their default values. And all non-optional arguments should be put before optional arguments in the function definition.
julia> foo(x, y=1, z=2; u, v=4) = x - (y + z) + u * v;
ERROR: syntax: invalid keyword argument u
julia> foo(x, y=1, z=2; u=3, v=u+1) = x - (y + z) + u * v;
julia> foo(1)
10
julia> foo(x, y=1, z=2, u; v=4) = x - (y + z) + u * v;
ERROR: syntax: optional positional arguments must occur at end
julia> foo(x, u, y=1, z=2; v=4) = x - (y + z) + u * v;
julia> foo(1, 3)
10
Ellipsis "...
" has special meaning when used with iterable objects
and functions.
-
Iterable objects followed by an ellipsis "
...
" as an argument to a function call.julia> add(x, y) = x + y; julia> a = (1, 2); b = [3, 4]; julia> add(a) ERROR: no method add((Int64,Int64),) julia> add(a...) 3 julia> add(b...) 7
Here the "
...
" in the function call tells Julia that the argumenta
should be treated as a collection of arguments. In other words, each element ina
should be considered as an individual argument to the functionadd
. -
An argument followed by an ellipsis "
...
" as the last argument in a function definition.julia> bar(a,b,x...) = (a,b,x); julia> bar(1,2) (1,2,()) julia> bar(1,2,3) (1,2,(3,)) julia> bar(1,2,3,4) (1,2,(3,4))
In this case, the "
...
" tells Julia that the functionbar
takes two or more arguments, the first asa
, the second asb
, and the remains asx
if any.
In Julia, for
loops, try
and catch
blocks, function
bodies, all of these constructs will introduce a new scope for the
variable used within them, where new local variables can be defined
and visible, without worrying about naming conflicts of the same name
variables inside and outside the scope. However, attention should be
paid to begin...end
blocks, since they won't introduce any new
scope:
julia> for i = 1
local x = 1
for j = 1
local x = 2
println(x)
end
println(x)
end
2
1
julia> begin
local x = 1
begin
local x = 2
println(x)
end
println(x)
end
ERROR: syntax: local x declared twice
let...end
is often used for introducing a new scope for local
variables without extra side effects like for...end
where the code
within it may loop multiple times. The template for let...end
is:
let var1 = value1, var2, var3 = value3
code
end
where the first line is used for defining local variables that can be
referred to within the "code
" part.
julia> x = 1; y = 2;
julia> let x = 2
y = 3x
end
6
julia> y
6
julia> x
1
julia> let x = x, y, z = 2
y = x + z
println(y)
end
3
julia> y
6
Note that in the first line of the last let...end
, the first x
is the new defined variable within let...end
, while the second
x
is the one defined previously.
One can also leave the first line blank.
julia> let
x = 2
y = 3x
end
6
julia> y
6
julia> x
2
An easily misleading thing I found in the Julia Manual about Scope of Variables is:
Fs = cell(2)
for i = 1:2
Fs[i] = ()->i
end
julia> Fs[1]()
1
julia> Fs[2]()
2
Actually, when there already exists the varialbe i
before for
loop:
julia> i = 1;
julia> for i = 1:2
Fs[i] = () -> i
end
julia> Fs[1]()
2
julia> Fs[2]()
2
the effect is the same as while
loop:
Fs = cell(2)
i = 1
while i <= 2
Fs[i] = ()->i
i += 1
end
julia> Fs[1]()
3
julia> Fs[2]()
3
unless one introduces a new scope by let...end
:
Fs = cell(2)
i = 1
while i <= 2
let i = i
Fs[i] = ()->i
end
i += 1
end
julia> Fs[1]()
1
julia> Fs[2]()
2
The last entry (and only it) in a conditional chain formed by the
logical operators &&
and ||
can be a type of non-boolean
expression, so that the value of the whole chain expression will be
either a boolean value (false
for &&
, and true
for ||
)
or the value of the last entry, depending on whether the last entry
got evaluated or not, which is determined by the short-circuit
property of logical operators. For example,
a = (false || (x = "Hello")) # a will be "Hello"
a = (true || (x = "Hello")) # a will be true
which can be re-written with one-line if
statements as:
a = (if !false x = "Hello" end)
a = (if false x = "Hello" else true end)
And I think no real code would be written this way. It is only for illustration. And the pratical usage of this behaviour of logical operators are well demonstrated by the factorial routine in the Julia manual - Short-Circuit Evaluation
As mnemonic, one can think:
(a || b)
as
if !a
b
end
and
(a && b)
as
if a
b
end
Though Julia is dynamic programming language, it doesn't mean type is not important. It is just the so powerful type system in Julia that make it expressive, clear and intuitive.
Usually, one doesn't need to specify a type for a value in Julia.
However, there is some time that it is clearer and more useful to
explicitly declare (or annotate) a type for a value. Type
declaration (or annotation) is done by following an expression
or a variable the operator ::
and the desired type:
julia> (1+1)::Int
2
julia> (1.0 + 1.0)::Int
ERROR: type: typeassert: expected Int64, got Float64
julia> add(x::Int, y::Int) = x + y
add (generic function with 1 method)
julia> add(1, 2)
3
julia> add(1.0, 2.0)
ERROR: no method add(Float64,Float64)
Note that type annotation cannot be used in global scope, such as the REPL.
julia> x::Int8
ERROR: x not defined
julia> x::Int8 = 10
ERROR: x not defined
Abstract types are the backbone of Julia type system and form the
conceptual hierachy of it. They make a piece of code can be applied
to a range of types not just a specific concrete type. In the
whole type hierarchy of Julia, there are two predefined abstract types
Any
and None
, which are at the top and the bottom respectively.
In other words, all objects are instances of Any
, and Any
is also
the supertype of all types. On the opposite, no object is an instance
of None
, and None
is the subtype of all types.
An abstract type can be defined by abstract
:
abstract Number
A hierarchical relationship between two abstract types is defined by
<:
:
abstract Real <: Number
where Real
is below Number
in the type hierarchy, in other words,
Real
is a subtype of Number
and Number
is a supertype of Real
.
<:
can also be used as an operator (or a function) to find out
whether one type is a subtype of the other type:
julia> Int <: Number
true
A type union is a special abstract type, much like union
in C, that
can be constructed by the Union
function:
julia> IntOrString = Union(Int,String)
Union(String,Int64)
julia> 1 :: IntOrString
1
julia> "Hello!" :: IntOrString
"Hello!"
julia> 1.0 :: IntOrString
ERROR: type: typeassert: expected Union(String,Int64), got Float64
Unlike abstract types, concrete types are used to refer to specific types that can have instances.
Most commonly-used user-defined concrete types are composite
types. Concrete typs is a collection of name fields, more like
struct
in C, but less like class
in C++. They are defined by
type...end
with field names and optionally annotated types:
julia> type Foo
bar
baz::Int
qux::Float64
end
Instances of type Foo
are created by applying the Foo
type object
like a function to values of compatible types with the fields:
julia> foo = Foo("Hello, world.", 23, 1.5)
Foo("Hello, world.",23,1.5)
julia> foo = Foo("Hello, world.", 23.0, 1.5)
ERROR: no method Foo(ASCIIString,Float64,Float64)
The list of field names of a composite type can be obtained by the
names
function:
julia> names(Foo)
3-element Array{Any,1}:
:bar
:baz
:qux
julia> names(foo)
3-element Array{Any,1}:
:bar
:baz
:qux
The value of a specific field can be accessed by following an instance
with a .
and the field name:
julia> foo = Foo("Hello, world.", 23, 1.5)
Foo("Hello, world.",23,1.5)
julia> foo.bar
"Hello, world."
julia> foo.bar = 1
1
julia> foo.bar
1
Parametric types are types that parameterized in terms of other
types. In other words, a parametric type is a collection of types
where each specific concrete type is derived from other corresponding
specific types in replace of type paramenters (such as the T
in
curly braces below of Point
) of that parametric type, much like a
function, however, it is types here being the substitutes, not values.
Therefore, a parametric type is also an abstract type.
One can define a parametric type by following the type name with a curly-braces-surrounded list of type paramenters:
julia> type Point{T}
x::T
y::T
end
julia> abstract Pointy{T}
julia> type Pointz{A,B}
x::A
y::B
end
julia> Point{Int} <: Point
true
julia> Pointy{Int} <: Pointy
true
julia> Point{Int} <: Point{Number}
false
julia> Pointy{Int} <: Pointy{Number}
false
Since a parametric type is an abstract type, instances can only be created for a specific concrete type derived from that parametric type. Except that we need to specify concrete types for the type paramenters, all are the same as other non-parametric types:
julia> Point{Float64}(1.0,2.0)
Point{Float64}(1.0,2.0)
julia> Pointz(1, "a")
Pointz{Int64,ASCIIString}(1,"a")
Just as a plain type can have a supertype, so the type parameter can also have a supertype serves as a range constraint:
julia> abstract Pointy{T <: Real}
julia> Pointy{Int64}
Pointy{Int64}
julia> Pointy{String}
ERROR: type: Pointy: in T, expected Real, got Type{String}
Like typedef
in C, typealias
is used to give a new name for an
type.
julia> typealias IntPt Point{Int64}
Point{Int64} (constructor with 1 method)
julia> IntPt(1, 2)
Point{Int64}(1,2)
Function | Description | Example |
---|---|---|
T1 <: T2 |
Subtype operator, determine whether T1 is a subtype of T2 . |
Int64 <: Number returns true |
isa(x, type) |
Determine whether x is of the given type . |
isa(1, Float64) returns false |
typeof(x) |
Get the concrete type of x . |
typeof(1) returns Int64 (in 64-bit system) |
super(type) |
Return the supertype of DataType type |
super(Int64) , super(Signed) , super(Integer) , super(Real) return Signed , Integer , Real , Number , respectively |
In Julia, a function can have different behaviors for different combinations of argument types and numbers. Such definition of one possible behavior for a fucntion is called a method, and it is just a new definition of the same function but with different arguments. Such mechanism of choosing which method to execute when a function is applied in Julia is known as multiple dispatch.
A new method for a specific function can be introduced anywhere at
anytime, as long as it has the same name and different argument types
or numbers. Argument types can be specified by ::
operator (see
also Type declaration), thus, the method can only
be applied to arguments of the specific types:
julia> f(x::Int64, y::Int64) = x + y
f (generic function with 1 method)
julia> f(3, 4)
7
julia> f(3.3, 4.4)
ERROR: no method f(Float64,Float64)
julia> f(x::Float64, y::Float64) = round(x + y)
f (generic function with 2 methods)
julia> f(3.3, 4.4)
8.0
Like parametric types, methods can also be parametric. Type parameters should be declared within curly brackets after the method name and before the argument tuple:
julia> same_type_number{T}(x::T, y::T) = T <: Number;
julia> same_type_number(x, y) = false
same_type_number (generic function with 2 methods)
julia> same_type_number(1, 2.0)
false
julia> same_type_number(1, 2)
true
The second method for same_type_number
above is used as a catch-all
method when the two arguments aren't the same type.
Function | Description | Example |
---|---|---|
methods(f) |
Show all methods of f with their argument types. |
Constructors are used for creating new objects of a type. By default, type objects serve as constructor functions, which means that a new object of a type can be created by following the type name by a tuple of field values at the same order of the fields (see Composite types).
There are two ways that a constructor can be defined.
-
One way is defining a constructor after the definition of the type. These kind of constructors are called outer constructors. It is much like defining a new method for a function, except that it must use one of the already existed constructors for creating a new object. And generally, the type name followed by a tuple of the field names is the automatically provided default one.
-
And the other is defining a constructor inside the definition of the type. These kind of constructors are called inner constructors. Once a inner constructor is defined, the automatically provided default constructors mentioned above will not provided.
julia> type OrderedPair
x::Real
y::Real
# a inner constructor that makes sure the first argument
# is less than or equal to the second argument
OrderedPair(a,b,c) = a > b ? error("out of order") : new(a+c,b+c)
end
julia> a = OrderedPair(1, 2, 3)
OrderedPair(4,5)
julia> a.x = 10 # But one can make x greater than y
10
julia> a
OrderedPair(10,5)
julia> b = OrderedPair(1, 2) # no default constructor if a inner one exists
ERROR: no method OrderedPair(Int64,Int64)
julia> OrderedPair(a) = OrderedPair(a, a, 2a) # outer constructor
OrderedPair (constructor with 2 methods)
julia> c = OrderedPair(1)
OrderedPair(3,3)
Julia is still young. The number of available packages is small compared to R. However, they grow rapidly. Here we list the number of available packages in the table. One may have different interpretations, but the purpose here is for studying the trend of these two counterparts only by tracking the number of packages newly comming out.
Date | Julia | R |
---|---|---|
2014-06-19 | 279 | 5323 |
2014-07-21 | 377 | 5413 |
2014-08-21 | 401 | 5516 |
2014-09-21 | 435 | 5633 |
2014-11-21 | 477 | 5775 |
2015-01-21 | 536 | 5954 |
2015-05-21 | 629 | 6308 |
2015-08-21 | 702 | 6644 |
2015-09-21 | 742 | 6772 |
2015-10-21 | 782 | 6881 |
2015-12-21 | 797 | 7099 |
2016-01-21 | 861 | 7207 |
2016-02-21 | 899 | 7332 |
2016-03-21 | 937 | 7455 |
We can get the number of available packages of Julia and R, respectively by the following Linux shell scripts.
# Get the number of available packages of Julia from ``Metadata for registered Julia packages''
expr $(git clone https://github.com/JuliaLang/METADATA.jl.git; ls METADATA.jl | wc -l) - 1; \
rm -rf METADATA.jl
# Or from Julia Package Listing. This may not be the same as that from Metadata
curl -s http://pkg.julialang.org/ | grep "<p>" | head -1 | cut -d'<' -f2 | cut -d' ' -f3
# Get the number of available packages of R from CRAN package list
curl -s https://cran.r-project.org/web/packages/available_packages_by_name.html | \
grep -F "<tr>" | wc -l
# Or from METACRAN
curl -s http://www.r-pkg.org/ | grep "active packages" | awk '{print $1}' | \
awk 'BEGIN { FS=","} {print $1$2}'
There is also a Searchable listing of all registered packages for Julia, where you can search for specific package of Julia.
- The Offical Website for Julia Programming Language
- The Latest Manual for Julia Lang
- The Stable Manual for Julia Lang
- Julia Bloggers gathers blogs about Julia. One can also submit their RSS feed for the specific blogs related to Julia
- List of Available Julia Packages