Day 1: Resistance Is Futile

Julia is a relatively new language, but the team has been incredibly productive. Even with three days, we won’t be able to cover it all.

Day 1 will cover the built-in types and operators. We’ll also look at Julia’s dictionaries and arrays. The arrays are particularly powerful, with the ability to slice and manipulate them in pieces or in multiple dimensions.

On the second day we’ll look at all the major control flow patterns like if, while, and for. We’ll look at user-defined types and functions, and discover Julia’s multiple dispatch. Concurrency will round out the day, allowing us to do computation in a distributed way.

Our final day, we’ll play with Julia’s macro system and then build an image codec using everything we’ve learned.

Before we get to all this code, we’ll need to install Julia.

Installing Julia

Prebuilt packages for Julia for Windows, OS X, and Linux can be downloaded from the Julia download site.[66] We’ll be using version 0.3.0 in this chapter, which is the current pre-release version.

If you can’t find a package for your system, you can install directly from source. The README.md file in the Julia repository contains detailed building instructions. Be warned that because it requires you to build LLVM as well, the build will take quite a while.

Once you have Julia installed, fire up the REPL by running julia, and you should see something like the following:

 
$ ​julia
 
_ _ _(_)_ | A fresh approach to technical computing
 
(_) | (_) (_) | Documentation: http://docs.julialang.org
 
_ _ _| |_ __ _ | Type "help()" to list help topics
 
| | | | | | |/ _` | |
 
| | |_| | | | (_| | | Version 0.3.0-prerelease+3551 (2014-06-07 20:57 UTC)
 
_/ |\__'_|_|_|\__'_| | Commit 547facf* (12 days old master)
 
|__/ | x86_64-apple-darwin12.5.0
 
 
julia>

The language is now at your fingertips. Let’s give it a whirl:

 
julia>​ println("Hello, world!")
 
Hello, world!
 
 
julia>

With the world properly greeted, let’s explore Julia’s syntax a bit.

Built-in Types

Every language has its atoms—its component parts. Most of Julia’s atoms should be familiar to you from other languages, but unlike most dynamic languages, Julia has more precise types.

We can explore the atoms and their types at the REPL using the typeof function:

 
julia>​ typeof(5)
 
Int64

There are floating-point numbers in several sizes: 16, 32, and 64 bits. Integers are even more diverse with both unsigned (Uint8, Uint16, etc.) and signed (Int8, Int16, etc.) variants. Numeric literals are interpreted in the most general way: Int64 and Float64.

 
julia>​ typeof(5.5)
 
Float64

// is used to make rational number literals.

 
julia>​ typeof(11//5)
 
Rational{Int64} (constructor with 1 method)

Symbols are convenient when you would otherwise use a string, but they are more efficient and easier to type and read. Julia has borrowed these from Lisp, Erlang, and Ruby.

 
julia>​ typeof(:foo)
 
Symbol
 
julia>​ typeof(true)
 
Bool
 
julia>​ typeof('a')
 
Char
 
julia>​ typeof("abc")
 
ASCIIString (constructor with 2 methods)
 
julia>​ typeof(typeof)
 
Function

Tuples are fixed-size groups of other types. Their type is a tuple of the types of their components.

 
julia>​ typeof((5, 5.5, "abc"))
 
(Int64,Float64,ASCIIString)

Arrays look as you’d expect. The extra 1 in the type signature is the array’s dimensionality, which we will learn more about later.

 
julia>​ typeof([1, 2, 3])
 
Array{Int64,1}

Dictionary literals use curly braces and =>, just like older versions of Ruby. The first parameter in the type signature is the type of the key, and the second is the type of the value. Any is Julia’s universal type.

 
julia>​ typeof({:foo => 5})
 
Dict{Any,Any} (constructor with 3 methods)

Now that we’ve seen what we have available, let’s do something with them.

Common Operators

Most of Julia’s numeric operators are exactly what you’d expect:

 
julia>​ 1 + 2
 
3

Numeric operations between different types of numbers auto-promote. Adding two integers gives an integer, but adding a float and an integer gives a float.

 
julia>​ 1 + 2.2
 
3.2

Division always returns a floating-point number, even when both arguments are integers.

 
julia>​ 5 / 1
 
5.0

The operator is the same as / but with the arguments reversed. Inverse division is primarily useful for linear algebra.

 
julia>​ 1 5
 
5.0

Truncating integer division can be done with div.

 
julia>​ div(7, 3)
 
2
 
julia>​ mod(7, 3)
 
1

Julia has bitwise operators, and you can use the bits function to see the binary representation of a value.

 
julia>​ bits(5)
 
"0000000000000000000000000000000000000000000000000000000000000101"
 
julia>​ bits(6)
 
"0000000000000000000000000000000000000000000000000000000000000110"
 
julia>​ 6 & 5
 
4
 
julia>​ 5 | 6
 
7

Bitwise negation is done with ~, and exclusive or is $.

 
julia>​ ~0
 
-1
 
julia>​ 5 $ 6
 
3

Boolean operators are the same as in C and Java, as are the operators for comparison.

 
julia>​ true || false
 
true
 
julia>​ true && false
 
false
 
julia>​ !true
 
false
 
julia>​ !!true
 
true
 
julia>​ mn < x < mx
 
true

You can assign to multiple variables at once using commas—a syntax borrowed from Python. The left and right sides must have the same structure.

 
julia>​ mn, x, mx = 1, 3, 5
 
(1,3,5)

All of these operators work on the simplest types in the language. Let’s look at some of Julia’s more complex types.

Dictionaries and Sets

Julia’s dictionaries are the same as those in other dynamic languages you might be familiar with, but they can be more explicitly typed. Keys and values in the dictionary must all be of the same type, but that type may be Any.

These two kinds of dictionaries—one dynamic and free and one restricted to specific types—both have their own literal syntax. When you want the typical dynamic behavior, use {...}, but if you want more explicit types, use [...]:

 
julia>​ implicit = {:a => 1, :b => 2, :c => 3}
 
Dict{Any,Any} with 3 entries:
 
:b => 2
 
:c => 3
 
:a => 1
 
 
julia>​ explicit = [:a => 1, :b => 2, :c => 3]
 
Dict{Symbol,Int64} with 3 entries:
 
:b => 2, :c => 3, :a => 1

(By default Julia lists entries one per line, but we’ll take the liberty of condensing its output throughout the chapter.)

Fetching keys and values from the dictionary is easy, as is testing for existence of an entry:

 
julia>​ explicit[:a]
 
1

get fetches a value by its key, returning the default value if the key isn’t found.

 
julia>​ get(explicit, :d, 4)
 
4

keys returns an iterator of all the keys.

 
julia>​ numbers = [:one => 1, :two => 2]
 
julia> the_keys = keys(numbers)
 
KeyIterator for a Dict{Symbol,Int64} with 2 entries. Keys:
 
:two, :one

collect constructs an array from the items in an iterator.

 
julia>​ collect(the_keys)
 
3-element Array{Symbol,1}:
 
:b, :c, :a

The in operator can be used to test if an item exists in an array or an iterator.

 
julia>​ :a in the_keys
 
true

The in function is exactly the same as the operator. It has special syntax support so you can use it either way. Note that for dictionaries, items are represented as a two-tuple of the key and value.

 
julia>​ in((:a, 1), explicit)
 
true

Julia also has a Set type, which is an unordered set, and functions to manipulate it.

No matter how many times a particular element is given in the constructor, it will appear only once in the set.

 
julia>​ a_set = Set(1, 2, 3, 1, 2, 3)
 
Set{Int64}({2, 3, 1})
 
julia>​ union(Set(1, 2), Set(2, 3))
 
Set{Int64}({2, 3, 1})
 
julia>​ intersect(Set(1, 2), Set(2, 3))
 
Set{Int64}({2})

Set difference subtracts all the elements of the second set from the first.

 
julia>​ setdiff(Set(1, 2), Set(2, 3))
 
Set{Int64}({1})
 
julia>​ issubset(Set(1, 2), Set(3, 4, 0, 1, 2))
 
true

These collection types are useful but unsurprising. Arrays in Julia, however, are straight out of the future.

Twenty-Fourth-Century Arrays

Julia’s arrays are powerhouses of functionality. You can create arrays of varying dimensions, slice out arbitrary regions (also in multiple dimensions), reshape them, or do complex operations on them. Like dictionaries and sets, they are typed and hold items all of the same type.

First, let’s look at how to create arrays.

Arrays constructed with [...] have their type inferred. Note that if a common type cannot be inferred, the root type Any is used. These Any arrays work much like arrays in other dynamic languages.

 
julia>​ animals = [:lions, :tigers, :bears]
 
3-element Array{Symbol,1}:
 
:lions, :tigers, :bears
 
julia>​ [1, 2, :c]
 
3-element Array{Any,1}:
 
1, 2, :c

If you want a particular type, you can use Type[...] to construct them. It causes an error if the types aren’t convertible to the target type.

 
julia>​ Float64[1, 2, 3]
 
3-element Array{Float64,1}
 
1.0, 2.0, 3.0

There are lots of functions in Julia’s standard library for creating arrays. Here are the most common ones:

 
julia>​ zeros(Int32, 5)
 
5-element Array{Int32,1}:
 
0, 0, 0, 0, 0
 
julia>​ ones(Float64, 3)
 
3-element Array{Float64,1}:
 
1.0, 1.0, 1.0
 
julia>​ fill(:empty, 5)
 
5-element Array{Symbol,1}:
 
:empty, :empty, :empty, :empty, :empty

These functions all take the type of the array (or the value in the case of fill) and the size as arguments.

Indexing and Slicing

Accessing elements of an array can be done with indexing or slicing, both of which use the familiar square bracket notation:

 
julia>​ animals = [:lions, :tigers, :bears]
 
3-element Array{Symbol,1}:
 
:lions, :tigers, :bears

Arrays in Julia are indexed from 1, not 0. This follows the mathematical convention.

 
julia>​ animals[1]
 
:lions

The end keyword is an alias for the last element of an array. This is similar to Python’s -1, but a bit more readable.

 
julia>​ animals[end]
 
:bears

Using : inside the brackets allows you to return slices of the array. This is a two-element slice, and it can be used just like any other array in Julia. A slice of a single element still returns an array.

 
julia>​ animals[2:end]
 
2-element Array{Symbol,1}:
 
:tigers, :bears
 
julia>​ animals[1:1]
 
1-element Array{Symbol,1}:
 
:lions

You can write to slices and indices as well. Indices can be assigned to, which mutates the array.

 
julia>​ animals[1] = :zebras
 
:zebras
 
julia>​ animals
 
3-element Array{Symbol,1}:
 
:zebras, :tigers, :bears

Even slices can be assigned to. If given a single element, it assigns that element to every position of the slice.

 
julia>​ animals[2:end] = :hippos
 
:hippos
 
julia>​ animals
 
3-element Array{Symbol,1}:
 
:zebras
 
:hippos
 
:hippos

You can also assign arrays to slices.

 
julia>​ animals[2:end] = [:sharks, :whales]
 
2-element Array{Symbol,1}:
 
:sharks, :whales
 
julia>​ animals
 
3-element Array{Symbol,1}:
 
:zebras, :sharks, :whales

Slices are really powerful and make it trivial to manipulate arrays in complex ways. Since you can use them just like regular arrays, you can pass them to functions so that those functions operate only on a subset of the data.

Now we’ll take arrays into another dimension.

Multidimensional Arrays

Julia is designed as a language for scientific and numerical programming. Those types of tasks typically involve a lot of linear algebra using vectors and matrices. Fortunately, Julia excels with amazing multidimensional arrays.

Let’s start by adding just one dimension. We’ll create, manipulate, and inspect a small matrix

To write literal arrays with two dimensions, use semicolons between the rows and leave out the commas between elements. Using commas and semicolons both is an error, and using no commas always creates a two-dimensional array, even when there are no semicolons.

 
julia>​ A = [1 2 3; 4 5 6; 7 8 9]
 
3x3 Array{Int64,2}:
 
1 2 3
 
4 5 6
 
7 8 9

You can retrieve the size and shape of the array using size. It returns a tuple of the length of the array in each dimension. Here our array is 3 by 3.

 
julia>​ size(A)
 
(3,3)

Use a comma to give multiple indices to an array. Each index is along the respective dimension. Here we ask for the third element of the second row.

 
julia>​ A[2,3]
 
6

Slicing works in arbitrary dimensions as well. This fetches the second column.

 
julia>​ A[1:end,2]
 
3-element Array{Int64,1}:
 
2
 
5
 
8

Using a slice with no bounds fetches all elements in that dimension.

 
julia>​ A[2,:]
 
1x3 Array{Int64,2}:
 
4 5 6

Setting a two-dimensional slice also works. This sets everything to zero except the top and left edges.

 
julia>​ A[2:end,2:end] = 0
 
0
 
julia>​ A
 
3x3 Array{Int64,2}:
 
1 2 3
 
4 0 0
 
7 0 0

All the array constructor functions take the size of the array as the second argument. Before we used an integer, but you can also pass a tuple for multidimensional arrays. rand generates an array with random elements, each between 0 and 1.

 
julia>​ rand(Float64, (3,3))
 
3x3 Array{Float64,2}:
 
0.12651 0.679185 0.052333
 
0.429212 0.0113811 0.886528
 
0.639923 0.0794754 0.917688

Common operators on matrices and vectors work out of the box. You can add, subtract, and multiply arrays element-wise or with matrix multiplication.

eye constructs the identity matrix. eye(N) makes an N×N matrix, and eye(M, N) creates an M×N matrix.

 
julia>​ I = eye(3, 3)
 
3x3 Array{Float64,2}:
 
1.0 0.0 0.0
 
0.0 1.0 0.0
 
0.0 0.0 1.0

Multiplying an array by a scalar value is done element-wise.

 
julia>​ I * 5
 
3x3 Array{Float64,2}:
 
5.0 0.0 0.0
 
0.0 5.0 0.0
 
0.0 0.0 5.0
 
 
julia>​ v = [1; 2; 3]

Using the dotted version of the * operator does explicit element-wise multiplication. Using * here would have been an error as two 3×1 vectors cannot be matrix multiplied.

 
julia>​ v .* [0.5; 1.2; 0.1]
 
3-element Array{Float64,1}:
 
0.5
 
2.4
 
0.3

Adding a quote after an array will transpose it; this is shorthand for the transpose function. A 1×3 vector times a 3×1 vector gives the dot product, resulting in a scalar result.

 
julia>​ v' * v
 
1-element Array{Int64,1}:
 
14

A 3×3 matrix multiplied with a 3×1 vector outputs a new 3×1 vector.

 
julia>​ [1 2 3; 2 3 1; 3 1 2] * v
 
3-element Array{Int64,1}:
 
14
 
11
 
11

Don’t worry too much if you aren’t familiar with linear algebra. The point is that Julia’s arrays and operators are custom made for doing linear algebra. Of course, they are capable of all the normal things arrays are useful for too.

What We Learned in Day 1

This has been a whirlwind tour of Julia’s types and operators, and we didn’t even cover much of its built-in library functions. Even with this humble beginning, you can imagine just how good of a number cruncher Julia is. It’s also built out of familiar pieces from dynamic languages; hopefully you feel right at home.

Julia has a lot of types that you’ve no doubt seen in other languages: symbols, integers, floats, dictionaries, sets, and arrays. Its operators hold few surprises.

Collection types are multifaceted, since Julia is strongly typed even though it is dynamic. Class dynamic language behavior is achieved through the Any type, but Java-like strongly typed behavior can be used too. Most of the arrays we dealt with were uniform Int64 or Float64. Arrays of only concrete types like this make for very efficient representation and computation.

Julia’s arrays are where the language really starts to shine. Not only does it have all the normal things you’d want from an array in Python or Java, but it also has powerful indexing and slices that work even in multiple dimensions. Arrays also support the common linear algebra operations in addition to element-wise operations.

Your Turn

It’s your turn to experiment with Julia’s types and common operations.

Find…

  • The Julia manual

  • Information about IJulia

  • The Julia language Reddit, which has blog posts and articles related to Julia

Do (Easy):

  • Use typeof to find the types of types. Try Symbol or Int64. Can you find the types of operators?

  • Create a typed dictionary with keys that are symbols and values that are floats. What happens when you add :thisis => :notanumber to the dictionary?

  • Create a 5×5×5 array where each 5×5 block in the first two dimensions is a single number but that number increases for each block. For example, magic[:,:,1] would have all elements equal to 1, and magic[:,:,2] would have all elements equal to 2.

  • Run some arrays of various types through functions like sin and round. What happens?

Do (Medium):

  • Create a matrix and multiply it by its inverse. Hint: inv computes the inverse of a matrix, but not all matrices are invertable.

  • Create two dictionaries and merge them. Hint: Look up merge in the manual.

  • sort and sort! both operate on arrays. What is the difference between them?

Do (Hard):

  • Brush off your linear algebra knowledge and construct a 90-degree rotation matrix. Try rotating the unit vector [1; 0; 0] by multiplying it by your matrix.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset