Day 3: Variables and Data Types

Most coding involves the assignment and manipulation of variables. Julia is dynamically typed, which means that you don’t need to declare explicitly a variable’s data type. It also means that a single variable name can be associated with different data types at various times. Julia has a sophisticated, yet extremely flexible, system for dealing with data types. covered in great detail by the official documentation. My notes below simply highlight some salient points I uncovered while digging around.

One of the major drawbacks of dynamically typed languages is that they generally sacrifice performance for convenience. This does not apply with Julia, as explained in the quote below.

The landscape of computing has changed dramatically over the years. Modern scientific computing environments such as MATLAB, R, Mathematica, Octave, Python (with NumPy), and SciLab have grown in popularity and fall under the general category known as dynamic languages or dynamically typed languages. In these programming languages, programmers write simple, high-level code without any mention of types like int, float or double that pervade statically typed languages such as C and Fortran.

Julia is dynamically typed, but it is different as it approaches statically typed performance. New users can begin working with Julia as they did in the traditional numerical computing languages, and work their way up when ready. In Julia, types are implied by the computation itself together with input values. As a result, Julia programs are often completely generic and compute with data of different input types without modification—a feature known as “polymorphism.”

Julia: A Fresh Approach to Numerical Computing

Variables and Assignment

Variable assignment and evaluation work in precisely the way you’d expect.

x = 3
3
x^2
9

Julia supports Unicode, so ß, Ɛ and Ȁ are perfectly legitimate (although possibly not too sensible) variable names.

ß = 3; 2ß
6

Chained and simultaneous assignments are possible.

a = b = c = 5
5
d, e = 3, 5
(3,5)

Multiple expressions can be combined into a single compound expression. Julia supports both verbose and compact syntax.

x = begin
  p = 2
  q = 3
  p + q
end
5
x = (p = 2; q = 3; p + q)
5

Constants, declared as const, are immutable variables (forgive the horrendous contradiction, but I trust that you know what I mean!).

const SPEED_OF_LIGHT = 299792458;

Somewhat disconcertingly it is possible to change the value of a constant. You just can’t change its type. This restriction has more to do with performance than anything else.

What about predefined constants?

pi
π = 3.1415926535897...
e
e = 2.7182818284590...
VERSION
v"0.3.10"

Data Types

The practical implications of picking number types are pretty important. Efficient programming requires you to use the least amount of memory. Julia in Action by Chris von Csefalvay

Julia has an extensive type hierarchy with its root at the universal Any type. You can query the current data type for a variable using typeof(). As mentioned above, this is dynamic and a variable can readily be reassigned a value with a different type.

x = 3.5;
typeof(x)
Float64
x = "I am a string";
typeof(x)
ASCIIString (constructor with 2 methods)

You can use isa() to check whether a variable or constant is of a particular type.

isa(x, ASCIIString)
true
isa(8, Int64)
true
isa(8, Number)
true
isa(8, ASCIIString)
false

We see that 8 is both a In64 and a Number. Not only does that make mathematical sense, it also suggests that there is a relationship between the In64 and Number data types. In fact Int64 is a subtype of Signed, which derives from Integer, which is a subtype of Real, which derives from Number

super(Int64)
Signed
super(Signed)
Integer
super(Integer)
Real
super(Real)
Number

Formally this can be written as

Int64 <: Signed <: Integer <: Real <: Number
true

where <: is the “derived from” operator. We can explore the hierarchy in the opposite direction too, where subtypes() descends one level in the type hierarchy.

subtypes(Integer)
5-element Array{Any,1}:
 BigInt
 Bool
 Char
 Signed
 Unsigned

Numeric Types

Julia supports integers between Int8 and Int128, with Int32 or Int64 being the default depending on the hardware and operating system. A “U” prefix indicates unsigned variants, like UInt64. Arbitrary precision integers are supported via the BigInt type.

Floating point numbers are stored by Float16, Float32 and Float64 types. Arbitrary precision floats are supported via the BigFloat type. Single and double precision floating point constants are given with specific syntax.

typeof(1.23f-1)
Float32
typeof(1.23e-1)
Float64

In Julia complex and rational numbers are parametric types, for example Complex{Float32} and Rational{Int64}. More information on complex and rational numbers in Julia can be found in the documentation.

(3+4im)^2
-7 + 24im
typeof(3+4im)
Complex{Int64} (constructor with 1 method)
typeof(3.0+4im)
Complex{Float64} (constructor with 1 method)
typeof(3//4)
Rational{Int64} (constructor with 1 method)

An encyclopaedic selection of mathematical operations is supported on numeric types.

1 + 2
3
1 / 2
0.5
div(5, 3), 5 % 3
(1,2)
sqrt(2)
1.4142135623730951

Characters and Strings

Julia distinguishes between strings and characters. Strings are enclosed in double quotes, while individual characters are designated by single quotes. Strings are immutable and can be either ASCII (type ASCIIString) or Unicode (type UTF8String). The indexing operator [] is used to extract slices from within strings. Evaluating variables within a string is done with a syntax which will be familiar to most shell warriors.

name = "Julia"
"Julia"
name[4]
'i'
name[end]
'a'
length(name)
5
"My name is $name and I'm $(2015-2012) years old."
"My name is Julia and I'm 3 years old."

There is a lot of functionality attached to the String class. To get an idea of the range, have a look at the output from methodswith(String).

Julia supports Perl regular expressions. Regular expression objects are of type Regex and defined by a string preceded by the character ‘r’ and possibly followed by a modifier character (‘i’, ’s’, ’m’ or ‘x’).

username_regex = r"^[a-z0-9_-]{3,16}$"
r"^[a-z0-9_-]{3,16}$"
ismatch(username_regex, "my-us3r_n4m3")
true
ismatch(username_regex, "th1s1s-wayt00_l0ngt0beausername")
false
hex_regex = r"#?([a-f0-9]{6}|[a-f0-9]{3})"i;
m = match(hex_regex, "I like the color #c900b5.")
RegexMatch("#c900b5", 1="c900b5")
m.match
"#c900b5"
m.offset
18

xkcd comic about the relationship between sexual arousal and consecutive vowels.

Explicit type conversion works either by using convert() or the lower-case type name as a function.

convert(Float64, 3)
3.0
float64(3)
3.0
int64(2.5)
3
string(2.5)
"2.5"

Type Specifications

Although Julia is dynamically typed, it’s still possible (and often desireable) to stipulate the type for a particular variable. Furthermore, from a performance perspective it’s beneficial that a variable retains a particular type once it has been assigned. This is known as type stability. We’ll find out more about these issues when we look at defining functions in Julia.

As before, my detailed notes on today’s foray into Julia can be found on GitHub.