# Structs and Objects

## Contents

# 11. Structs and Objects¶

We have seen a number of different *types* in Julia, such as `Int64`

, `Float64`

, `Array{Float64,2}`

, etc. Now we will learn how to create new types, how to use them to store data, and how to define new functions (methods) and operators on them. This is closely related to the concept of *object orientation* in other computer languages.

## 11.1. Composite types¶

The keyword `struct`

is used to create a so-called composite type, that is, a new user-defined type.
The type is given a name, and a list of *fields* (or *attributes*) which are names that will be used
as variables to store data for objects of the new type.

As an example, we create a new type named `MyPoly`

for storing and operating on polynomials.
The fields will be an array of coefficients `c`

, and a character `var`

to denote the name of the dependent
variable.

A variable name can also be followed by `::`

and a type. This will enforce this type and give an
error otherwise, which can be useful to ensure the new composite type is used correctly. In our
example, we specify that `c`

must be of type `Vector`

(however we do not specify which types of
numbers in the vector). We also specify that `var`

must be a `Char`

.

```
struct MyPoly
c::Vector
var::Char
end
```

The coefficients `c`

will represent the polynomial in the standard monomial form:

where \(c\) is the array of coefficients, \(d\) is the polynomial degree, and \(n\) is the length of the array \(c\). Note that we allow for \(n\) to be greater than the number of required coefficients \(d+1\), and that the coefficients are stored in reverse order (that is, highest exponents come first).

With this definition, we can create a so-called *instance* of the type
(or *object*) by
providing the values for each of the fields. For example, the polynomial
\(p(x) = 3x^2 - 5x + 2\) can be created as:

```
p = MyPoly([3,-5,2], 'x')
```

```
MyPoly([3, -5, 2], 'x')
```

Note that by default, Julia will print the object using the type name and a list of the field values. Later we will override this and create a specialized output function.

You can define other ways to specify (or initialize) a polynomial using a so-called *constructor*,
which is a function that is called when a type is initiated. For example, if you want to allow for
creating a polynomial using only a coefficient array `c`

but using the default value `x`

for the `var`

field, you can add a so-called outer constructor as shown below:

```
MyPoly(c) = MyPoly(c, 'x')
```

```
MyPoly
```

At this point, the struct `p`

does not do anything besides simply storing the variables `c`

and `var`

. They can be accessed using a `.`

notation:

```
p.c
```

```
3-element Vector{Int64}:
3
-5
2
```

```
p.var
```

```
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
```

However, structs in Julia are *immutable* which means that once created, you can not change their contents. This is different from many other language, and there are some good reasons for this design. If you need to change the variables in a struct, or even add new fields, simply use the keyword `mutable struct`

instead of `struct`

. However, here we will stay with a standard struct, since in our example we will only modify the polynomials coefficients `c`

. This is an exception which actually is allowed, since the variable `c`

is itself a mutable object and its content can be changed.

For example, if you want to change the polynomial to \(p(x) = -x^3 + 3x^2 - 5x + 2\) you can *not* change the actual array in `c`

:

```
p.c = [-1,3,-5,2] # Error: Immutable struct
```

but you can change the content in the existing array `c`

:

```
resize!(p.c, 4)
p.c[1:4] = [-1,3,-5,2]
p
```

```
MyPoly([-1, 3, -5, 2], 'x')
```

Of course, another option is to simply create a new instance of the `MyPoly`

type:

```
p = MyPoly([-1,3,-5,2], p.var)
```

```
MyPoly([-1, 3, -5, 2], 'x')
```

## 11.2. Functions on types¶

We can easily define functions on new types, by passing the objects as arguments or return values. For example, we can easily define a function that multiplies a polynomial \(p(x)\) by \(x\) in the following way:

```
function times_x(p)
return MyPoly(vcat(p.c,0), p.var)
end
times_x(p)
```

```
MyPoly([-1, 3, -5, 2, 0], 'x')
```

However, this function should only work if you pass a `MyPoly`

type to it. In Julia you can write functions that operate differently on different types. A function which defines the behavior for a specific combination or number of arguments is called a *method*.

A function which is specialized for a certain type can be created using a *type declaration* with the `::`

operator. As an example, we create a `degree`

function to find the degree \(d\). The implementation
is straight-forward, we simply search the for the first (highest degree) non-zero
coefficient. Note that we define the degree of the zero polynomial to be -1.

```
function degree(p::MyPoly)
ix1 = findfirst(p.c .!= 0)
if ix1 == nothing
return -1
else
return length(p.c) - ix1
end
end
```

```
degree (generic function with 1 method)
```

```
println(degree(MyPoly([0,0,0,0,0])))
println(degree(MyPoly([0,0,0,0,1])))
println(degree(MyPoly([0,0,0,1,0])))
println(degree(MyPoly([1,0,0,0,0])))
```

```
-1
0
1
4
```

By specializing the `degree`

function to the `MyPoly`

type, we can now use the same function name for other combinations or types, that is, to implement other methods. This is also called *overloading*.

```
function degree(p::Int)
println("degree function called with Int argument")
end
function degree(p)
println("degree function called with any other argument")
end
degree(1.234)
degree([1,2])
degree(-123)
degree(MyPoly([1,2,3]))
```

```
degree function called with any other argument
degree function called with any other argument
degree function called with Int argument
```

```
2
```

## 11.3. Customized printing¶

Julia provides a special function `show`

in the `Base`

package, which can be overloaded to change how objects of a new type are printed. For our polynomials, instead of showing the array of coefficients `c`

and the name of the independent variable `var`

, we will write it as a polynomial in standard math notation.

The details of this functions do not matter much, it mostly needs to deal with certain special cases to print polynomials correctly. The main point is that it will *only be called* for objects of type `MyPoly`

.

```
function Base.show(io::IO, p::MyPoly)
d = degree(p)
print(io, "MyPoly: ")
for k = d:-1:0
coeff = p.c[end-k]
if coeff == 0 && d > 0
continue
end
if k < d
if isa(coeff, Real)
if coeff > 0
print(io, " + ")
else
print(io, " - ")
end
coeff = abs(coeff)
else
print(io, " + ")
end
end
if isa(coeff, Real)
print(io, coeff)
else
print(io, "($coeff)")
end
if k == 0
continue
end
print(io, "⋅", p.var)
if k > 1
print(io, "^", k)
end
end
end
```

```
p
```

```
MyPoly: -1⋅x^3 + 3⋅x^2 - 5⋅x + 2
```

```
MyPoly([-1.234,0,0,0,4.321], 's')
```

```
MyPoly: -1.234⋅s^4 + 4.321
```

## 11.4. Callable objects¶

One basic operation to perform on a polynomial is evaluation, that is, for a given number \(x\) compute \(p(x)\). We will implement this using *Horner’s rule*:

While we could implement this in a function with a new name, for example, `polyval`

, Julia allows the definition of a method which behaves like a function evaluating the polynomial:

```
function (p::MyPoly)(x)
d = degree(p)
v = p.c[end-d]
for cc = p.c[end-d+1:end]
v = v*x + cc
end
return v
end
```

```
println(p(1))
println(p(1.234))
println(p.([1,-2,1.3])) # Note: Broadcasting automatically defined
```

```
-1
-1.4808129039999995
[-1.0, 32.0, -1.6270000000000002]
```

## 11.5. Plotting¶

Using the evaluation functionality, we can easily implement plotting of polynomials. Again we reuse the name `plot`

which is already used in the `PyPlot`

package, but we specialize it to our `MyPoly`

type:

```
using PyPlot
function PyPlot.plot(p::MyPoly, xlim=[-2,2])
xx = collect(range(xlim[1], xlim[2], length=100))
plot(xx, p.(xx))
xlabel(string(p.var))
end
```

```
p = MyPoly([1,1,-5,-5,4,4])
plot(p)
p
```

```
MyPoly: 1⋅x^5 + 1⋅x^4 - 5⋅x^3 - 5⋅x^2 + 4⋅x + 4
```

## 11.6. Operator overloading¶

Operators such as `+`

can also be overloaded for new types, by specializing the function definition. These operators are so fundamental to the language itself that they are part of the `Base`

package. This special package is automatically included by Julia, so there is no need to call `using Base`

before using any of its functionality or specify `Base.func_name()`

when calling one of its functions. However, when overloading a function in this package, we must still specify that the function is a part of this package with the usual syntax `Base.func_name()`

.

Adding polynomials is of course easy, we simply add the coefficients. However, for our implementation we first need to make sure the coefficient vectors are long enough to contain the sum:

```
function Base.:+(p1::MyPoly, p2::MyPoly)
if p1.var != p2.var
error("Cannot add polynomials of different independent variables.")
end
d1 = degree(p1)
d2 = degree(p2)
d = max(d1,d2)
c = [fill(0, d-d1); p1.c] + [fill(0, d-d2); p2.c]
return MyPoly(c, p1.var)
end
```

```
println(p)
println(p + p)
println(p + MyPoly([1.1,2.2]))
println(p + MyPoly([1], 's')) # will trigger our error message
```

```
MyPoly: 1⋅x^5 + 1⋅x^4 - 5⋅x^3 - 5⋅x^2 + 4⋅x + 4
MyPoly: 2⋅x^5 + 2⋅x^4 - 10⋅x^3 - 10⋅x^2 + 8⋅x + 8
MyPoly: 1.0⋅x^5 + 1.0⋅x^4 - 5.0⋅x^3 - 5.0⋅x^2 + 5.1⋅x + 6.2
```

```
Cannot add polynomials of different independent variables.
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:33
[2] +(p1::MyPoly, p2::MyPoly)
@ Main ./In[19]:3
[3] top-level scope
@ In[20]:4
[4] eval
@ ./boot.jl:360 [inlined]
[5] include_string(mapexpr::typeof(REPL.softscope), mod::Module, code::String, filename::String)
@ Base ./loading.jl:1116
```

Subtraction is easiest done by overloading the `-`

operator and reusing the implementation of `+`

:

```
function Base.:-(p1::MyPoly, p2::MyPoly)
return p1 + MyPoly(-p2.c)
end
```

Similarly with scalar multiplication, we overload `*`

. Note that we do not specify the type of the first scalar argument `a`

, it is assumed that it is a regular number (not a `MyPoly`

) object. We also define multiplication in the reverse order, by reusing the same function (since we know that it commutes).

```
function Base.:*(a, p::MyPoly)
newc = a * p.c
return MyPoly(newc, p.var)
end
function Base.:*(p::MyPoly, a)
return a*p
end
```

Using the overloaded operators `+`

, `-`

, and `*`

(for scalars), we can perform many polynomial operations:

```
p1 = 0.4p
p2 = p1 - .3*MyPoly([-2,1,1])
p3 = -1p2 + p
plot.([p1,p2,p3]);
```

## 11.7. Generic programming¶

In the examples above we have specialized many functions and operators to work in a specific way for objects of type `MyPoly`

. However, note the advantages of *not* limiting the type of a variable or function argument to be of a certain type. For example, in the definition of `MyPoly`

we did not specify that the coefficients `c`

should be of e.g. integer or floating point types (in fact our examples used both). This means our functions work perfectly fine also for rational coefficients and arguments:

```
p = MyPoly([1, -2//3, 6//7])
```

```
MyPoly: 1//1⋅x^2 - 2//3⋅x + 6//7
```

```
p.([1, -7//2])
```

```
2-element Vector{Rational{Int64}}:
25//21
1297//84
```

and for complex:

```
p = MyPoly([1, im, -1, -im, 1])
```

```
MyPoly: (1 + 0im)⋅x^4 + (0 + 1im)⋅x^3 + (-1 + 0im)⋅x^2 + (0 - 1im)⋅x + (1 + 0im)
```

```
p.([0, im, -1 - im])
```

```
3-element Vector{Complex{Int64}}:
1 + 0im
5 + 0im
-2 + 1im
```

or for `BigFloat`

:

```
p = MyPoly(collect(-1.5:3.5))
```

```
MyPoly: -1.5⋅x^5 - 0.5⋅x^4 + 0.5⋅x^3 + 1.5⋅x^2 + 2.5⋅x + 3.5
```

```
p(BigFloat(-π))
```

```
405.2722682884305251107738095974542912801020079018409933037640550322547603287204
```