# Matrix multiplication with custom types

Hey there.

So I’m very new to coding with Julia and am trying to get a handle on the “generalizability” of the language, as I’ll call it.

Below I created a Point strcut/class/? and defined the + and * operations of two points. I want to figure out how to take this and make the last line work somewhat like regular matrix multiplication works. Just instead of scalars the entries of the resulting matrix would be Point’s. Could someone point me in the direction to look to do this?

``````import Base.+
import Base.*

struct Point
x::Number
y::Number
end

+(p1::Point, p2::Point) = Point(p1.x + p2.x, p1.y + p2.y)
*(p1::Point, p2::Point) = Point(p1.x * p2.x, p1.y * p2.y)

a = Point(1,1)
b = Point(2,2)
c = a + b

point_matrix = [Point(i, j) for i in 1:3, j in 4:6]
point_matrix

point_vector = [a; b; c]
point_vector

point_matrix * point_vector
``````

Below is the error that is generated

MethodError: no method matching zero(::Point)
Closest candidates are:
zero(!Matched::Type{LibGit2.GitHash}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LibGit2/src/oid.jl:220
zero(!Matched::Type{Pkg.Resolve.VersionWeights.VersionWeight}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/resolve/VersionWeights.jl:19
zero(!Matched::Type{Pkg.Resolve.MaxSum.FieldValues.FieldValue}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/Pkg/src/resolve/FieldValues.jl:44

Stacktrace:
 generic_matvecmul!(::Array{Point,1}, ::Char, ::Array{Point,2}, ::Array{Point,1}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LinearAlgebra/src/matmul.jl:542
 mul! at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LinearAlgebra/src/matmul.jl:76 [inlined]
 *(::Array{Point,2}, ::Array{Point,1}) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.1/LinearAlgebra/src/matmul.jl:50
 top-level scope at In:1

Also, if anyone could give me formatting tips or a link to a guide on how to make a better post that would be great!

Welcome to Julia!

So the error you’re running into is that it wants a `zero` function defined, which it uses at some point during the matrix multiplication algorithm. So it wants you to define a function `zero(::Point) = ...` that returns a zero object for the `Point` type, i.e. something so that that `x + zero(Point) == x` whenever `x` is a `Point` (in other words, `zero(T)` should return the additive identity for things of type `T`, but it doesn’t know what this is for `Point`s, so it wants you to define the method).

Formatting tips: PSA: how to quote code with backticks

1 Like

Thanks for that! Still getting used to understanding the errors in Julia. I added the lines

``````import Base.zero
zero(x::Point) = Point(0,0)
``````

to the code and it worked.

Is there a place that indicates the types of functions I have to import from Base to get them to work like this? I’ll look through the docs a bit.

The answer is “each of them that you use” (any function from Base that you want to override requires that you do one of two things)

``````import Base: +, *, zero

zero(x::MyType) = ...
``````

or (does not require the import statement)

``````Base.zero(x::MyType) = ...
``````

also, your type will work more quickly if you define it this way

``````struct Point{T<:Real}
x::T
y::T
end
``````

Just to add, with the parametric definition (`struct Point{T <: Real}`), you would want to define `zero` as

``````import Base.zero
zero(::Point{T}) where {T} = Point(zero(T), zero(T))
``````

so that way you get `Point`'s with the right numeric type:

``````julia> zero(Point(1.0, 2.0))
Point{Float64}(0.0, 0.0)

julia> zero(Point(1, 2))
Point{Int64}(0, 0)
``````

This actually illustrates one of the advantages of the parametric definition; without it, you don’t know ahead of time what the numeric types of `p.x` and `p.y` are for a `Point` `p`, but in the parametric case you do since you have access to the parameter `T` to use in `zero`.

(You could define `zero(p::Point) = Point(zero(p.x), zero(p.y))` in the non-parametric case, but this involves a runtime lookup to figure out the type of `p.x` in order to choose the right `zero` method to use when you call `zero(p.x)`, whereas in the parametric case it’s all done at compile time.)

1 Like

Thanks for the extra tidbit Jeffrey!

Thanks for the detailed explanation Eric! I’ll be sure to look this up.

2 Likes