Charlie Harvey

Seven More Languages: Julia day two

Day 2 of Julia takes a wander through control flow, types, concurrency and multiple dispatch. Multiple dispatch is something I have not played with much before, and it seems very useful indeed. It works like overridden methods in OOP. But rather than picking which function to call based on just the object, in multiple dispatch all the types are taken into account. Pretty cool.

I enjoyed playing with the parallel processing parts of the language too. It is remarkably straightforward to parallellize tasks, which has got to be pretty critical for a language that is optimized for use in scientific computing.

Exercises

Easy exercises

Write a for loop that counts backward using Julia’s range notation.

We can use the start:step:end notation like this.

julia> for i in 10:-1:1 println(i) end 10 9 8 7 6 5 4 3 2 1 julia> for i in 10:-2:5 println(i) end 10 6 8

Write an iteration over a multidimensional array like [1 2 3; 4 5 6; 7 8 9]. In what order does it get printed out?

Seems trivial to get rows out thus.

julia> m = [1 2 3; 4 5 6; 7 8 9] 3x3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 julia> for i in m println(m[i]) end 1 2 3 4 5 6 7 8 9

Use pmap to take an array of trial counts and produce the number of heads found for each element.

I spent a really long time on this and could not find a sensible way to do it. This seems very clearly a job for a fold/reduce rather than a map. I came up with something that "works" but not sure if it is what the authors were after.

julia> heads=[1 0 1 1; 1 1 0 1; 1 0 0 1; 0 1 0 1] 4x4 Array{Int64,2}: 1 0 1 1 1 1 0 1 1 0 0 1 0 1 0 1 julia> pmap(x->sum(heads[x,:]), 1:4) 4-element Array{Any,1}: 3 3 2 2

Medium exercises

Write the fatorial function as a parallel for loop

Well, there is a much prettier way to do it that I realized earlier which is just.

julia> *(1:5...) 120

However, to make a parallel loop you could

julia> function fac(n) @parallel (*) for i in 1:n i end end fac (generic function with 1 method) julia> fac(5) 120 julia> fac(15) 1307674368000 julia> fac(25) 7034535277573963776

Add a method for concat that can concatenate an integer with a matrix — concat(5, [1 2; 3 4]) should produce [5 5 1 2; 5 5 3 4]

That is a bit weird. I would have thought it would produce [5 1 2; 5 3 4], so I implemented it that way. I use the library function hcat once I have constructed a vercor of the correct dimension.

julia> q = [1 2 5; 2 2 2; 6 6 6] 3x3 Array{Int64,2}: 1 2 5 2 2 2 6 6 6 julia> function concat(n, m) tocat = fill(n,size(m)[1]) hcat(tocat,m) end concat (generic function with 1 method) julia> concat(5,q) 3x4 Array{Int64,2}: 5 1 2 5 5 2 2 2 5 6 6 6

You can extend built-in functions with new methods too. Add a new method for + to make "jul"+"ia" work.

I assumed that + ought to mean string concatnenation rather than anything more exotic here.

julia> function +(a::String, b::String) "$a$b" end + (generic function with 118 methods) julia> "jul"+"ia" "julia"

Hard exercises

Parallel for loops dispatch loop bodies to other processes. Depending on the size of the loop body, this can have a noticeable overhead. See if you can beat Julia’s parallel for loop version of pflip_coins by writing something using the lower-level primitives like @spawn or remotecall.

I wasn’t able to have a major impact on execution time — the variance between runs was more than any saving in time over the original function. However taking a more functional approach allowed me to use significantly less memory. The code is a little more complex syntactically and there is a potential bug if the test number is not divisible by 4 (I found that running 4 processes gave me the best results).

I suppose that the saving is because of the count_flip function body being added to the global environment with @everywhere but I don’t have any evidence for this.

Incidentally an optimization that sometimes works in Haskell — combining the functions in the 2 maps into one map with 2 functions in it — which in this case would mean composing fetch and spawn in the same map call actually made things somewhat slower.

julia> @everywhere function count_flip(n) count = 0 for i in 1:n count += int(randbool()) end count end julia> function mycoins(n) sum( map( fetch, map(x -> (@spawn count_flip(n/4)), 2:5) ) ) end mycoins (generic function with 1 method) julia> @time pflip_coins(1000000000) elapsed time: 2.150548463 seconds (6429340 bytes allocated) 500020611 julia> @time pflip_coins(1000000000) elapsed time: 1.713294895 seconds (183048 bytes allocated) 499988013 julia> @time mycoins(1000000000) elapsed time: 1.681171545 seconds (20912 bytes allocated) 444429739 julia> @time mycoins(1000000000) elapsed time: 1.638956568 seconds (18944 bytes allocated) 444437840

Wrapping up

Julia is looking like a really nice language so far.

The syntax is straightforward. There are types but they don’t get in your way. The REPL works nicely — in particular the standard readline facilities work in a more sensible way than other REPLs when you’re writing multiline functions. The concurrency tools I have looked at are clean and easy to use. Not bad at all.


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.