Julia has two different kinds of types: bittypes (like `Int64`

, `Int32`

, `UInt32`

and `Char`

) and composite types.

Here is an example of an inbuilt composite type representing complex numbers, for example, $$x= 1+i$$

In [110]:

```
x=1+2im
```

Out[110]:

In [111]:

```
typeof(x)
```

Out[111]:

A complex number consists of two fields: a real part (denoted `re`

) and an imaginary part (denoted `im`

). Fields of a type can be accessed using the `.`

notation:

In [112]:

```
x.re
```

Out[112]:

In [113]:

```
x.im
```

Out[113]:

We can also make our own types. Let's make a type to represent complex numbers in the format
$$z=rexp(i\theta)$$
That is, we want to create a type with two fields: `r`

and `θ`

. This is done using the `type`

syntax, followed by a list of names for the fields, and finally the keyword `end`

In [101]:

```
type MyComplex
r
θ
end
```

In [102]:

```
z=MyComplex(1,0.1)
```

Out[102]:

We can access fields for our new type using `.r`

and `.θ`

:

In [103]:

```
z.r,z.θ
```

Out[103]:

Functions are created using the keyword `function`

, followed by a name for the function, and in parentheses a list of arguments. Let's make a function that takes in a single number $x$ and returns $x^2$.

In [104]:

```
function sq(x)
x^2
end
```

Out[104]:

In [105]:

```
sq(2),sq(3)
```

Out[105]:

Multiple arguments to the function can be included with `,`

. Here's a function that takes in 3 arguments and returns the average. (We write it on 3 lines only to show that functions can take multiple lines.)

In [108]:

```
function av(x,y,z)
ret=x+y
ret=ret+z
ret/3
end
```

Out[108]:

In [109]:

```
av(1,2,3)
```

Out[109]:

Variables live in different scopes. In the previous example, `x`

, `y`

, `z`

and `ret`

are *local variables*: they only exist inside of `av`

. So this means `x`

and `z`

are *not* the same as our complex number `x`

and `z`

defined above.

Warning: if you reference variables not defined inside the function, they will use the outer scope definition. The following example shows that if we mistype the first argument as `xx`

, then it takes on the outer scope definition `x`

, which is a complex number

In [114]:

```
function av2(xx,y,z)
(x+y+z)/3
end
```

Out[114]:

In [115]:

```
av2(5,6,7)
```

Out[115]:

You should almost never use this feature!! We should ideally be able to predict the output of a function from knowing just the inputs.

We can define functions for other types, for example vectors. Let's create a function that calculates the average of the entries of a vector. We want the function to work for general length vectors, so let's create vectors `v`

and `w`

of different lengths:

In [116]:

```
v=rand(Int,5)
w=rand(Int,10)
length(v),length(w)
```

Out[116]:

To implement the function, we need to use a for loop, using the `for`

keyword. The following syntax evaluates the body of the for loop (between the lines after the `for`

and before the `end`

) one by one for `k`

equal to every number in the range `1:10`

.

In [118]:

```
for k=1:5
k2=k^2
println(k2)
end
```

This is exactly the same as the following block of text, but without having to write it out explicitely

In [119]:

```
k=1
k2=k^2
println(k2)
k=2
k2=k^2
println(k2)
k=3
k2=k^2
println(k2)
k=4
k2=k^2
println(k2)
k=5
k2=k^2
println(k2)
```

We can use a for loop to step `k`

through every index of a vector. The following calculates the sum of the entries of the vector, printing out the current value for each value of k

In [120]:

```
v=[1,5,6,3]
ret=0
for k=1:length(v)
ret=ret+v[k]
println("At step $k, the current sum is $ret")
end
ret
```

Out[120]:

We are now ready to write a function that calculates the average of the entries of a vector:

In [39]:

```
function vecav(v)
ret=0
for k=1:length(v)
ret=ret+v[k]
end
ret/length(v)
end
```

Out[39]:

In [122]:

```
vecav([1,5,2,3,8,2])
```

Out[122]:

julia has an inbuilt `sum`

command that we can use to check our code:

In [123]:

```
sum([1,5,2,3,8,2])/6
```

Out[123]:

functions can be defined only for specific types using `::`

after the variable name. The same function name can be used with different type signatures.

The following defines a function `mydot`

that calculates the dot product, with a definition changing depending on whether it is an `Integer`

or a `Vector`

. Note that `Integer`

means any kind of integer: `mydot`

is defined for pairs of Int64's, Int32's, etc.

In [124]:

```
function mydot(a::Integer,b::Integer)
a*b
end
function mydot(a::Vector,b::Vector)
# we assume length(a) == length(b)
ret=0
for k=1:length(a)
ret=ret+a[k]*b[k]
end
ret
end
```

Out[124]:

In [125]:

```
mydot(5,6) # calls the first definition
```

Out[125]:

In [126]:

```
mydot(Int8(5),Int8(6)) # also calls the first definition
```

Out[126]:

In [127]:

```
mydot([1,2,3],[4,5,6]) # calls the second definition
```

Out[127]:

In [128]:

```
mydot([1,2,3,4],[4,5,6]) # an error is thrown because length(a) > length(b)
```

We should actually check that the lengths of `a`

and `b`

match. Let's rewrite `mydot`

using an `if`

, `else`

statement. The following code only does the for loop if the length of a is equal to the length of b, otherwise, it throws an error.

Note that `==`

checks if two quantities are equal. This is *not the same* as `=`

, which assigns the value of one quantity to the other

If we name something with the exact same signature (name, and argument types), previous definitions get overriden.

In [129]:

```
function mydot(a::Vector,b::Vector)
ret=0
if length(a) == length(b)
for k=1:length(a)
ret=ret+a[k]*b[k]
end
else
error("arguments have different lengths")
end
ret
end
```

Out[129]:

In [130]:

```
mydot([1,2,3,4],[5,6,7,8])
```

Out[130]:

In [131]:

```
mydot([1,2,3,4],[5,6,7])
```

Let's return to the example we started last lecture:

In [57]:

```
r=Ref(1)
r.x
```

Out[57]:

`Ref`

is just a composite type with a single field called `x`

. We can make our own version of `Ref`

called `MyRef`

:

In [132]:

```
type MyRef
x
end
```

The function call `MyRef(52)`

creates a new `MyRef`

, with `x`

initialized as 52:

In [133]:

```
myref=MyRef(52)
```

Out[133]:

In [134]:

```
myref.x
```

Out[134]:

we can create another variable `n`

that is equal to `myref`

.

In [135]:

```
n=myref
```

Out[135]:

Unlike bittypes, the fields of composite types point to locations in memory that store the values. In this example, `myref.x`

lives somewhere in memory, let's say at address 1543. But setting `n=myref`

has the property that all the fields of `n`

also point to the same location in memory. This means `n.x`

also points to address 1543.

So if we change the value of `myref.x`

to 6, this changes the value living in address 1543 to 6, and so `n.x`

is also automatically 6:

In [136]:

```
myref.x=6
```

Out[136]:

In [137]:

```
n.x
```

Out[137]:

This is very different from bittypes. Here, `myrefx`

and `nx`

are in two different locations in memory, let's say 1765 and 1987, and the `=`

copies the value 52 from `myrefx`

's address 1765 to `nx`

's address 1987. Then calling `myrefx=6`

actually creates a new address in memory, let's say 2076, with the value of 6. But `nx`

still corresponds to 1987, and is still 52.

In [138]:

```
myrefx=52
nx=myrefx
myrefx=6
nx
```

Out[138]:

Here is another example. Let's return to the composite type set-up:

In [139]:

```
myref=MyRef(52)
myref.x
n=myref
```

Out[139]:

If instead of calling `myref.x=6`

we call `myref=MyRef(6)`

, this creates a brand new `MyRef`

,
with the new `myref.x`

pointing to a new address in memory (let's say 6543) initialized with
the value 6. Whereas `n.x`

still points to the same address in memory as the old `myref.x`

,
so is still 52:

In [67]:

```
myref=MyRef(6)
```

Out[67]:

In [68]:

```
n.x
```

Out[68]:

Vectors behave like composite types, where they point to an address in memory, and `=`

copies the address in memory:

In [141]:

```
v=[1,2,3,4]
w=v
```

Out[141]:

You can change values of a vector using brackets and =:

In [142]:

```
v[2]=52
```

Out[142]:

This has changed v:

In [72]:

```
v
```

Out[72]:

But it's also changed w, since w points to the same location in memory as v:

In [143]:

```
w
```

Out[143]:

If we assign v to a new vector, w still points to the old location in memory, so is unchanged.

Makes sure it is clear the difference between `v=`

, which reassigns the variable v to a new value, and `v[1]=`

, which leaves v the same, but modifies a value in memory.

In [74]:

```
v=[6,75]
```

Out[74]:

In [75]:

```
w
```

Out[75]:

If you actually want to copy the entries of a vector, without pointing to a new vector, use `copy`

:

In [147]:

```
v=[6,75]
w=v
w2=copy(v)
```

Out[147]:

In [148]:

```
v[1]=2
```

Out[148]:

In [150]:

```
w
```

Out[150]:

In [149]:

```
w2
```

Out[149]:

Floating point numbers represent real numbers. They are also a bitstype, by default `Float64`

which uses 64 bits, but not we interpret the bits in a different way than integers.

In [153]:

```
x=1/2
typeof(x)
```

Out[153]:

In [154]:

```
y=1/3
typeof(y)
bits(y)
```

Out[154]:

We can create floats by adding .0 to the end. The following creates a Float64 to represent the integer 1:

In [155]:

```
x=1.0
bits(x)
```

Out[155]: