Confused with type definition

Hello there, say I want to create a new type which is basically the same of Array{Float64, 1} except for the behaviour for one operator (for example +).

What I’m doing now is the following. First create a new mutable type MyVec

struct MyVec
  v::Array{Float64, 1}
end

and then overloading the operator I need as

Base.:+(v::MyVec, u::MyVec) = MyVec(v.v .+ u.v[end:-1:1])

Now the problem I’m facing is that the rest of the operators shouldn’t change their behaviour, but I’m forced to re-define all of them.

Is it possible to tell Julia that MyVec is basically an Array{Float64, 1} so that I can “inherit” all the Array behaviour and simply add the new one?

That feature does not exist in Julia itself. There are approaches that can ease the work.

import Base: length
import LinearAlgebra: dot

 struct MyVec{T} <: AbstractVector{T}
   vec::Vector{T}
 end

# required by the AbstractArray interface
Base.size(x::MyVec) = size(x.vec)
Base.getindex(x::MyVec, i::Int) = getindex(x.vec, i)
Base.setindex!(x::MyVec, v, i::Int) = setindex!(x.vec, v, i)

# your customization of +
Base.:(+)(v::MyVec, u::MyVec) = ...
Base.:(+)(v::MyVec, u::Vector{Float64}) = v + MyVec(u)
Base.:(+)(v::Vector{Float64}, u::MyVec) = MyVec(v) + u

for F in (:length,) # add other appropriate unary functions
  @eval begin
    $F(v::MyVec,) = length(v.v)
  end
end

for F in (:dot,) # add other appropriate binary functions
  @eval begin
    $F(v::MyVec, u::MyVec) = $F(v.v, u.v)
    $F(v::MyVec, u::Vector{Float64}) = $F(v.v, u)
    $F(v::Vector{Float64}, u::MyVec) = $F(v, u.v)
  end
end
``

Why not just define a function

add(x::Vector{T}, y::Vector{T}) where {T} =
   dot(x,y) + dot(y, reverse(x))

or whatever you want it to do –
rather than needing all the special type support

1 Like

I see, thanks @JeffreySarnoff for the @eval example.

Creating a new type seems a way to keep the code cleaner (I also don’t want to change show behaviour for Array) but I’ll probably stick to Array and create only the new functions

Consider also doing MyVec <: AbstractVector{Float64}

1 Like

would that be in the struct definition, like

struct MyVec <: AbstractVector{Float64}
  v::Vector{Float64}
end

?

Yes, in this way your new datastructure can be used in all methods which accept AbstractVector

edited my example accordingly

not sure why it’s not working in the REPL tho

julia> struct MyVec <: AbstractVector{Float64}
         v::Vector{Float64}
       end

julia> MyVec([0.0, 0.0, 0.0])
Error showing value of type MyVec:
ERROR: MethodError: no method matching size(::MyVec)
Closest candidates are:
  size(::AbstractArray{T,N}, ::Any) where {T, N} at abstractarray.jl:38
  size(::Base.AsyncGenerator) at asyncmap.jl:409
  size(::Core.Compiler.StmtRange) at show.jl:1874
  ...

It does not work (or rather, cannot be printed) because a subtype of AbstractVector must implement some methods, see here.

That happens on showing the structure, add ; at the end of the line to not show it. To fully implement the iteration interface see Interfaces · The Julia Language

I see!

thanks for pointing out the interfaces docs page

I am kinda surprised nobody mentioned Lazy.jl and its macro @forward.

Basically you can do:

import Base: length, getindex, setindex
using Lazy: @forward

struct MyVec <: AbstractVector{Float64}
  v::Vector{Float64} # same as Array{Float64, 1}
end

@forward MyVec.v Base.size, Base.getindex, Base.setindex!

For all function you just delegate to the field.

12 Likes

thanks @Henrique_Becker !

@forward provides the cherry on top of the previous answers :slight_smile:

1 Like