Author Archives: Andrew Collier

#MonthOfJulia Day 11: Metaprogramming

Julia-Logo-Metaprogramming

Metaprogramming in Julia is a big topic and it’s covered extensively in both the official documentation as well as in the Introducing Julia wikibook. The idea behind metaprogramming is to write code which itself will either generate or change other code. There are two main features of the language which support this idea:

  • code representation (expressions and symbols) and
  • macros.

Code Representation

A symbol (data type Symbol) represents an unevaluated chunk of code. As such, symbols are a means to refer to a variable (or expression) itself rather than the value it contains.

julia> n = 5                               # Assign to variable n.
5
julia> n                                   # Refer to contents of variable n.
5
julia> typeof(n)
Int64
julia> :n                                  # Refer to variable n itself using quote operator.
:n
julia> typeof(:n)
Symbol
julia> eval(:n)
5
julia> E = :(2x + y)                       # Unevaluated expression is also a symbol.
:(2x + y)
julia> typeof(E)
Expr

The quote operator, :, prevents the evaluation of its argument.

Expressions are made up of three parts: the operation (head), the arguments to that operation (args) and finally the return type from the expression (typ).

julia> names(E)
3-element Array{Symbol,1}:
 :head
 :args
 :typ 
julia> E.head
:call
julia> E.args
3-element Array{Any,1}:
 :+   
 :(2x)
 :y   
julia> E.typ
Any

We can evaluate an expression using eval(). Not only does eval() return the result of the evaluated expression but it also applies any side effects from the expression (for example, variable assignment).

julia> x = 3; y = 5; eval(E)
11
julia> eval(:(x = 4))
4
julia> eval(E)
13

No real surprises there. But the true potential of all this lies in the fact that the code itself has an internal representation which can be manipulated. For example, we could change the arguments of the expression created above.

julia> E.args[3] = :(3y)                   # 2x + y becomes 2x + 3y
:(3y)
julia> E
:(2x + 3y)
julia> eval(E)
21

That still seems a little tame. What about manipulating a function?

julia> F = :(x -> x^2)
:(x->begin  # none, line 1:
            x^2
        end)
julia> eval(F)(2)                          # Evaluate x -> x^2 for x = 2
4
julia> F.args[2].args[2].args[3] = 3       # Change function to x -> x^3
3
julia> eval(F)(2)                          # Evaluate x -> x^3 for x = 2
8

Macros

Macros are a little like functions in that they accept arguments and return a result. However they are different because they are evaluated at parse time and return an unevaluated expression.

julia> macro square(<span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="">x)
       	:($x</span></span></span></span></span> * $x)
       end
julia> @square(5)
25
julia> @square 5
25
julia> macroexpand(:(@square(x)))
:(x * x)
julia> macroexpand(:(@square(5)))
:(5 * 5)
julia> macroexpand(:(@square(x+2)))
:((x + 2) * (x + 2))

macroexpand() is used to look at the code generated by the macro. Note that parentheses were automatically inserted to ensure the correct order of operations.

Julia has a plethora of predefined macros which do things like return the execution time for an expression (@time), apply an assertion (@assert), test approximate equality (@test_approx_eq) and execute code only in a UNIX environment (@unix_only).

The fact that one can use code to build and edit other code made me start thinking about self-replicating machines, self-reconfiguring modular robots, grey goo and utility fog. If we can do it in software, why not in hardware too? More evidence of my tinkering with metaprogramming in Julia can be found on github. No self-reconfiguring modular robots though, I’m afraid.


The post #MonthOfJulia Day 11: Metaprogramming appeared first on Exegetic Analytics.

#MonthOfJulia Day 10: Modules

Julia Module

Modules allow you to encapsulate your code and variables. In the words of the Julia documentation:

Modules in Julia are separate global variable workspaces. Modules allow you to create top-level definitions without worrying about name conflicts when your code is used together with somebody else’s. Within a module, you can control which names from other modules are visible (via importing), and specify which of your names are intended to be public (via exporting).
Modules, Julia Documentation

To illustrate the concept, let’s define two new modules:

julia> module AfrikaansModule
       __init__() = println("Initialising the Afrikaans module.")
       greeting() = "Goeie môre!"
       bonappetit() = "Smaaklike ete"
       export greeting
       end
Initialising the Afrikaans module.
julia> module ZuluModule
       greeting() = "Sawubona!"
       bonappetit() = "Thokoleza ukudla"
       end

If an __init__() function is present in the module then it’s executed when the module is defined. Is it my imagination or does the syntax for that function have an uncanny resemblance to something in another popular scripting language?

The greeting() function in the above modules does not exist in the global namespace (which is why the first function call below fails). But you can access functions from either of the modules by explicitly giving the module name as a prefix.

julia> greeting()
ERROR: greeting not defined
julia> AfrikaansModule.greeting()
"Goeie môre!"
julia> ZuluModule.greeting()
"Sawubona!"

The Afrikaans module exports the greeting() function, which becomes available in the global namespace once the module has been loaded.

julia> using AfrikaansModule
julia> greeting()
"Goeie môre!"

But it’s still possible to import into the global namespace functions which have not been exported.

julia> import ZuluModule.bonappetit
julia> bonappetit()
"Thokoleza ukudla"

In addition to functions, modules can obviously also encapsulate variables.

That’s pretty much the essence of it although there are a number of subtleties detailed in the official documentation. Well worth a look if you want to suck all the marrow out of Julia’s modules. As usual the code for today’s flirtation can be found on github.

The post #MonthOfJulia Day 10: Modules appeared first on Exegetic Analytics.

#MonthOfJulia Day 9: Input/Output

Your code won’t be terribly interesting without ways of getting data in and out. Ways to do that with Julia will be the subject of today’s post.

Julia-Logo-IO

Console IO

Direct output to the Julia terminal is done via print() and println(), where the latter appends a newline to the output.

julia> print(3, " blind "); print("mice!n")
3 blind mice!
julia> println("Hello World!")
Hello World!

Terminal input is something that I never do, but it’s certainly possible. readline() will read keyboard input until the first newline.

julia> response = readline();
Yo!
julia> response
"Yo!n"

Reading and Writing with a Stream

Writing to a file is pretty standard. Below we create a suitable name for a temporary file, open a stream to that file, write some text to the stream and then close it.

filename = tempname()
fid = open(filename, "w")
write(fid, "Some temporary text...")
close(fid)

print() and println() can also be used in the same way as write() for sending data to a stream. STDIN, STDOUT and STDERR are three predefined constants for standard console streams.

There are various approaches to reading data from files. One of which would be to use code similar to the example above. Another would be to do something like this (I’ve truncated the output because it really is not too interesting after a few lines):

julia> open("/etc/passwd") do fid
           readlines(fid)
       end
46-element Array{Union(UTF8String,ASCIIString),1}:
 "root:x:0:0:root:/root:/bin/bashn"                                               
 "daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinn"                               
 "bin:x:2:2:bin:/bin:/usr/sbin/nologinn"                                          
 "sys:x:3:3:sys:/dev:/usr/sbin/nologinn"                                          
 "sync:x:4:65534:sync:/bin:/bin/syncn"    

Here readlines() returns the entire contents of the file as an array, where each element corresponds to a line of content. readall() would return everything in a single string. A somewhat different approach would be to use eachline() which creates an iterator allowing you to process each line of the file individually.

Delimited Files

Data can be read from a delimited file using readdlm(), where the delimiter is specified explicitly. For a simple Comma Separated Value (CSV) file it’s more direct to simply use readcsv().

julia> passwd = readdlm("/etc/passwd", ':');
julia> passwd[1,:]
1x7 Array{Any,2}:
 "root"  "x"  0.0  0.0  "root"  "/root"  "/bin/bash"

The analogues writedlm() and writcecsv() are used for writing delimited data.

These functions will be essential if you are going to use Julia for data analyses. There is also functionality for reading and writing data in a variety of other formats like xls and xlsx, HDF5, Matlab and Numpy data files and WAV audio files.

File Manipulation

Julia implements a full range of file manipulation methods (documented here), most of which have names similar to their UNIX counterparts.

A few other details of my dalliance with Julia’s input/output functionality can be found on github.

The post #MonthOfJulia Day 9: Input/Output appeared first on Exegetic Analytics.