Add Base operator function to custom struct automatically

I have a struct:

struct DF{T}
    index
    value::T
end

df=DF([:a,:b,:c],[1,2,3]);

When I need a add method to DF and Vector, I have to do this:

Base.:(+)(df::DF,x)=DF(df.index,df.value + x)

df + [1,2,3]

DF{Vector{Int64}}([:a, :b, :c], [2, 4, 6])

In a upcoming day, if I need a sub method, I have to add method:

Base.:(-)(df::DF,x)=DF(df.index,df.value - x)

Is there a way I can add a method to DF, then DF will have the + -... methods automatically?

some_functions(df,DF,x)=DF(df.index,some_functions(df.value,x))

Well, in this case, you can write macros yourself, e.g.

julia> macro make(sym)
           return :(($sym)(df::DF, x)=DF(df.index, $(sym)(df.value, x)))
       end
@make (macro with 1 method)

julia> @make Base.:(+)

julia> DF([:a,:b,:c],[1,2,3]) + [1,2,3]
DF{Vector{Int64}}([:a, :b, :c], [2, 4, 6])

This makes it simple enough to create new functions.

If you want, you may wrap another macro to create +-*/... functions with one single call, that’s up to you.

1 Like

Thanks for you help. macro method you mentioned is fine, but there must be some more simpy method. I found it in pakage AxisArrays GitHub - JuliaArrays/AxisArrays.jl: Performant arrays where each dimension can have a named axis with values.

Here I can use the package like this:

> using AxisArrays


> v1=AxisArray([1,2,3],[:a,:b,:c])

1-dimensional AxisArray{Int64,1,...} with axes:
    :row, [:a, :b, :c]
And data, a 3-element Vector{Int64}:
 1
 2
 3

> v1 + [1,2,3]

3-element Vector{Int64}:
 2
 4
 6

> @which v1 + [1,2,3]
+(A::AbstractArray, B::AbstractArray) in Base at arraymath.jl:6

As you can see, when I call v1 + [1,2,3], the add method worked in v1.value . I thought there must be a + method attaced to Bae.:(+)(v::AxisArray,z::Vector), but when I call @which v1 + [1,2,3], the result show that it derectively call the original Base.+ rather than some custom added method. So I would guess the author of the package add some other more lower function to AxisArray struct.

Here it falls back to + defined on AbstractArray, because AxisArray is subtyped based on AbstractArray as shown below (from AxisArray impl)

struct AxisArray{T,N,D,Ax} <: AbstractArray{T,N}
    data::D  # D <:AbstractArray, enforced in constructor to avoid dispatch bugs (https://github.com/JuliaLang/julia/issues/6383)
    axes::Ax # Ax<:NTuple{N, Axis}, but with specialized Axis{...} types
    AxisArray{T,N,D,Ax}(data::AbstractArray{T,N}, axs::Tuple{Vararg{Axis,N}}) where {T,N,D,Ax} = new{T,N,D,Ax}(data, axs)
end

So, it works if you also implement your type to be a subtype of AbstractArray (iff it is feasible in your case…!!)

1 Like

this only makes sense if DF <: Number, and if this is the case (logically / in your mental model), then you can do:
https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Promotion

because we have this defined:

1 Like

Yes, this is exactly what I try to understand, Thanks very much.

1 Like

Yes, it makes sense only when DF isa AbstratArray. Thanks very much.