Chapter 2: Variables

We have, of course, encountered variables in the previous chapter, where we used some for string substitution. For a functional programming language, mastery of how variables work is essential. Julia is extremely flexible when it comes to variables, but wielding this flexibility will require finesse.

Assigning Variables

To assign a variable, simply use = (a single equals sign). That, really, is it. Nice, huh?


In [2]:
m = 2.32


Out[2]:
2.32

In [3]:
m


Out[3]:
2.32

Accessing an undefined variable yields an exception, of course:


In [4]:
n


LoadError: UndefVarError: n not defined
while loading In[4], in expression starting on line 1

Julia does not require you to explicitly declare variables before assignment (indeed, there is no useful way to do so).

Variable naming

In general, you can use just about anything you can type as a variable name. This includes Unicode characters from a quite astounding range.

In the following, we will be proving, using the comparison operator (>), that winter is not warmer than summer:


In [5]:
 = -12


Out[5]:
-12

In [6]:
 = 27


Out[6]:
27

In [7]:
 > 


Out[7]:
false

The above is, believe it or not, perfectly valid Julia. Whether it is perfectly sensible Julia, too, is a different question.

In general, using Unicode variable names for anything but commonly accepted scientific symbols, such as ћ, is not the best idea, lest you might leave readers of your code trying to figure out which of the thirty or so similar looking Unicode symbols you meant.

The stylistic convention of Julia is to use lowercase variable names, with words separated by _ (underscore), and only if necessary for the sake of legibility. Variables may not start with numbers and exclamation marks.

Goodwin's dream

In 1894, an amateur mathematician from Indiana, Edward J. Goodwin, proposed a way to square the circle (a feat proven to be impossible by the Lindeman-Weierstrass theorem proven a decade or so earlier). He lobbied long enough for his 'new mathematical truth' to be introduced as a Bill in the Indiana House of Representatives, in what became known as the Indiana Pi Bill of 1897. This inferred a value of approximately 3.2 for π. Fortunately, the intervention of Purdue professor C.A. Waldo helped to defeat the already much-ridiculed bill in the Senate, and the matter was laid to rest.

Julia, on the other hand, allows you to redefine the value of π. The following is entirely correct Julia:


In [8]:
π


Out[8]:
π = 3.1415926535897...

In [9]:
π = 3.2


WARNING: imported binding for π overwritten in module Main
Out[9]:
3.2

In [10]:
π


Out[10]:
3.2

It is, on the other hand, really bad practice to redefine set constants. Therefore, should you encounter the opportunity to redefine π, learn from the sad case of Mr. Goodwin and try to resist the temptation.

Assigning variables to variables

It is important to understand what the effect of assigning a variable to a variable is. It is perfectly valid Julia to have multiple variables point at each other.

However, doing so creates a 'shallow copy' – that is, a reference to the same memory address of where the original copy is located.

If you modify one variable pointing at it, the value of the other changes, too. In the following example, we will be creating an array m, then set n be equal to m. We then carry out an operation that changes the value of m, marked by the exclamation mark (don't worry if that part is new to you, it will be explained in Chapter 6 – for now, all you need to know is that pop! takes the last element of an indexable collection, returns it and removes it from the collection).

When we then call n, we see that its value, too, has been affected by the operation – that is because it did not so much have a value but acted merely as a reference to 'whatever is in m'.


In [11]:
m = [1,2,3,4]    # Creating array


Out[11]:
4-element Array{Int64,1}:
 1
 2
 3
 4

In [12]:
n = m    # Setting n to point to m


Out[12]:
4-element Array{Int64,1}:
 1
 2
 3
 4

In [13]:
pop!(m)    # Altering m
m


Out[13]:
3-element Array{Int64,1}:
 1
 2
 3

In [14]:
n    # n is also changed as a result.


Out[14]:
3-element Array{Int64,1}:
 1
 2
 3

We will be considering a way to get around this by 'deep copying' a value in a later chapter.

Literals

Literals are ways to enter various data types, whether into the REPL or a program.

Strings

We have already encountered string literals. To enter a string literal, simply delimit it by " (double quotation marks). Unlike Python or JavaScript, Julia does not accept single quotation marks.


In [16]:
"Hi Github!"


Out[16]:
"Hi Github!"

In [17]:
'Hi Github!'


LoadError: syntax: invalid character literal
while loading In[17], in expression starting on line 1

One-dimensional arrays

1D array literals are delimited by square brackets: [].

Each element they contain has to be either a variable or an otherwise valid literal, but they do not all have to be the same type (something true for most collections within Julia). Values are separated by a , (comma):


In [18]:
arr1 = [1, 2, "sausage", π]


Out[18]:
4-element Array{Any,1}:
 1         
 2         
  "sausage"
 3.2       

Ranges and range arrays

In Julia, a range is simply a shorthand for a sequence of numbers that are all spaced equally.

A range can be created using the range() function as range(start, end), but it is usually denoted in a shorthand literal, start:end. Ranges are interpreted as arrays, and you can create range arrays, arrays that are formed from a range, by simply enclosing the range notation in brackets:


In [22]:
[0:10]


Out[22]:
11-element Array{Int64,1}:
  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10

As you can see, a range in Julia includes both its start and end element.

A range doesn't have to be created between integers – [0.5:3.5] returns:


In [23]:
[0.5:3.5]


Out[23]:
4-element Array{Float64,1}:
 0.5
 1.5
 2.5
 3.5

An array may have an optional middle step attribute, which takes the form start:step:end.

To obtain an array of the numbers from 0 to 30 in steps of 10, you would enter the range array literal [0:10:30]:


In [28]:
[0:10:30]


Out[28]:
4-element Array{Int64,1}:
  0
 10
 20
 30

Multidimensional arrays

In some languages, multidimensional arrays can be created by enclosing one-dimensional arrays in another array, and so on.

Unfortunately, that is not the case in Julia, so [[1,2],[3,4]] would yield the one-dimensional array [1,2,3,4]:


In [51]:
non_md_array = [[1,2,3,4],[3,4,5,6],[5,6,7,8],[7,8,9,10]]


Out[51]:
16-element Array{Int64,1}:
  1
  2
  3
  4
  3
  4
  5
  6
  5
  6
  7
  8
  7
  8
  9
 10

In [65]:
still_non_md_array = [[0:10:30]; [0:10:30]]


Out[65]:
8-element Array{Int64,1}:
  0
 10
 20
 30
  0
 10
 20
 30

Rather, to create a multidimensional array, separate values by empty space and rows by a ; (semicolon).


In [58]:
fancy_md_array = [[1 2  3 4]; [3 4 5 6]; [5 6 7 8]; [7 8 9 10]] # with brackets


Out[58]:
4x4 Array{Int64,2}:
 1  2  3   4
 3  4  5   6
 5  6  7   8
 7  8  9  10

In [63]:
bracketless_md_array = [1 1 2 3 5; 8 13 21 34 55; 89 144 233 377 610] # without brackets


Out[63]:
3x5 Array{Int64,2}:
  1    1    2    3    5
  8   13   21   34   55
 89  144  233  377  610

Alternatively, you can enter columns, using square brackets (remember not to let your ingrained reflexes from Python take over and put a comma between the arrays!):


In [72]:
spaced_md_array = [[1,2,3] [4,5,6]] # without semicolons


Out[72]:
3x2 Array{Int64,2}:
 1  4
 2  5
 3  6

In [73]:
step_based_md_array = [[0:10:30] [0:10:30]]


Out[73]:
4x2 Array{Int64,2}:
  0   0
 10  10
 20  20
 30  30

When entering a multidimensional array, each row has to have the same length. Failing to do so raises an error:


In [74]:
md_sparse_array = [1 1 2; 8 13 21 34 55]


LoadError: ArgumentError: argument count does not match specified shape (expected 6, got 8)
while loading In[74], in expression starting on line 1

 in hvcat at abstractarray.jl:962

Tuples

Tuples are similar to one-dimensional arrays, in that they consist of a number of values. They are, however, unlike array literals in that they are immutable – once assigned, you cannot change the values in a tuple. Tuples are delimited by () (round brackets) and values are separated by commas.


In [75]:
fibo_tuple = (1, 1, 2, 3, 5)


Out[75]:
(1,1,2,3,5)

To demonstrate the difference between arrays and tuples, consider the following:


In [78]:
fibo_array = [1, 1, 2, 3, 5]


Out[78]:
5-element Array{Int64,1}:
 1
 1
 2
 3
 5

In [81]:
fibo_arr[2] = 0 # Change the value at index 2 to 0


Out[81]:
0

In [82]:
fibo_arr # Show the new array


Out[82]:
5-element Array{Int64,1}:
 1
 0
 2
 3
 5

In [83]:
fibo_tuple[2] = 0 # Set tuple value at index 2 to 0


LoadError: MethodError: `setindex!` has no method matching setindex!(::Tuple{Int64,Int64,Int64,Int64,Int64}, ::Int64, ::Int64)
while loading In[83], in expression starting on line 1

In this listing, we have created an array with the first five non-zero elements of the Fibonacci sequence. We then have used an accessor (fibo_arr[2] is an accessor that retrieves the second element of the array fibo_arr) to change the second value in the array to zero.

Calling the array shows that this was successful. On the other hand, trying to do the same with the tuple fibo_tuple of the same values that we declared earlier yields an error. This is because tuples are immutable; the values they are created with are their final values.

Dicts

Dicts are what in some other languages are known as associative arrays or maps: they contain key-value pairs.

A dict is created using the Dict() type. Individual mappings (key-value pairs) are separated by comma, and keys are separated from values by => (a double-arrow).


In [91]:
statisticians = Dict("Gosset" => "1876-1937", "Pearson" => "1857-1936", "Galton" => "1822-1911") # Current syntax


Out[91]:
Dict{ASCIIString,ASCIIString} with 3 entries:
  "Galton"  => "1822-1911"
  "Pearson" => "1857-1936"
  "Gosset"  => "1876-1937"

In [92]:
bracketed_statisticians = ["Gosset" => "1876-1937", "Pearson" => "1857-1936", "Galton" => "1822-1911"] # Old syntax


WARNING: deprecated syntax "[a=>b, ...]" at In[92]:1.
Use "Dict(a=>b, ...)" instead.
Out[92]:
Dict{ASCIIString,ASCIIString} with 3 entries:
  "Galton"  => "1822-1911"
  "Pearson" => "1857-1936"
  "Gosset"  => "1876-1937"

Sets

Sets are similar to other collections in that they contain various values. However, unlike arrays and tuples, they are unordered and unique-constrained: no element may occur multiple times within the set.

Sets do not have a specific notation, but rather are created using a syntax we will use a for creating all kinds of objects: by using the type (Set) and entering the values to constitute the set in whatever form they come in (typically arrays with square braces):


In [108]:
numbers = Set([1,2,3,4,1])


Out[108]:
Set([4,2,3,1])

In [111]:
numbers = Set(["Hi","Hi"])


Out[111]:
Set(ASCIIString["Hi"])

In [112]:
stooges = Set(["Moe", "Curly", "Larry"])


Out[112]:
Set(ASCIIString["Moe","Larry","Curly"])

Sets do accept duplicates at time of construction, but the resulting set will still only contain one of each unique element:


In [113]:
beatles = Set(["Lennon", "McCartney", "Harrison", "Starr", "Lennon"]) # The first Lennon is getting kicked out of the Beatles


Out[113]:
Set(ASCIIString["Starr","Harrison","McCartney","Lennon"])

Sets are unordered, meaning that two sets containing the same elements are equal:


In [114]:
Set(["Marsellus", "Jules", "Vincent"]) == Set(["Jules", "Vincent", "Marsellus"])


Out[114]:
true

An empty set is created by


In [118]:
Set([])


Out[118]:
Set{Any}()

In [117]:
Set()


Out[117]:
Set{Any}()

Conclusion

In this chapter, we have learned how to construct a handful of literals for the main data types and assign them to variables. This should give us a promising start for our next chapter, in which we are going to explore collections.