Overload broadcasting for custom struct?

Hello!

I have a struct defined as:

struct T3{T}
           x::T
           y::T
           z::T
end

I want to overload it such that when I write:

T3(1,1,1) .* 5 = T3(5,5,5)

But I do not know how to overload “.*”. Can anyone help?

Kind regards

1 Like

Here is the specification of the interface: Interfaces · The Julia Language.

However, I would say that in general customizing broadcasting is an advanced topic. For example if you want to have a look how much code was needed to add broadcasting to DataFrames.jl you can have a look here: https://github.com/JuliaData/DataFrames.jl/blob/master/src/other/broadcasting.jl.

2 Likes

If do not exactly understand what you want to achieve.

If you define the * operator for a T3{T} like this:

struct T3{T}
           x::T
           y::T
           z::T
end
import Base.*
function (*)(t3::T3{T}, n::Number) where {T}
    return T3{T}(t3.x*n,t3.y*n,t3.z*n)
end  

Then you can write:


julia> t=T3{Float64}(3,4,5)
T3{Float64}(3.0, 4.0, 5.0)

julia> t*5
T3{Float64}(15.0, 20.0, 25.0)

This can be broadcasted for a collection of t3:


julia> vt=[T3{Float64}(1,2,3),T3{Float64}(4,5,8),T3{Float64}(7,8,9)]
3-element Array{T3{Float64},1}:
 T3{Float64}(1.0, 2.0, 3.0)
 T3{Float64}(4.0, 5.0, 8.0)
 T3{Float64}(7.0, 8.0, 9.0)

julia> vt.*=5
3-element Array{T3{Float64},1}:
 T3{Float64}(5.0, 10.0, 15.0)
 T3{Float64}(20.0, 25.0, 40.0)
 T3{Float64}(35.0, 40.0, 45.0)


2 Likes

Broadcasting might be useful to define if @Ahmed_Salih wanted to take advantage of fusion in broadcasting (but as I have commented - this is an advanced topic so probably not the first thing to start with).

1 Like

Thanks @LaurentPlagne, that works for me, thanks for showing me how to use it as well!

@bkamins it was a bit more advanced than I thought, but I need to learn somehow :slight_smile: Fun to know this is possible in Julia

@LaurentPlagne just curious, is it possible to make it so that the order of multiplication does not matter?

It allows me to do T3 * number, but not number * T3

Kind regards

Glad that is useful !
For your second question see the discussion here

2 Likes

Thanks for the explanation! It makes more sense now that not in all cases it is wanted to have commutative properties. I didn’t completely understand the point of having “number as subtype”, but I think I will stick to your approach for now.

Kind regards

Note that it is not too long to add;

 (*)(n::Number, t3::T3{T}) where {T} = t3*n

Concerning the n::Number type anotation, I am not sure that it is idiomatic.
The type annotations are of special interest to organize the dispatch of the methods (as it is the case for the t3::T3{T} argument.

Here, since we do not do something specific with the Numbers, it maybe better to let it without annotations.

You don’t need the type parameter T and the where clause here, since you’re not using it.

2 Likes

Thanx ! It takes time to forgot about the C++ verbosity :smiley:

(*)(n, t3::T3) = t3*n

I hate reviving this thread, but it shows often in search engines, so other people may benefit from this. PR #26891 implemented a data structure for fused broadcasting. This means we can overload this structure to specialize broadcasted operations. In OP’s example this would be done as:

struct T3{T}
           x::T
           y::T
           z::T
end
function Broadcast.broadcasted(::typeof(*), a::T3{T}, b) where T
    return T3{T}(a.x*b, a.y*b, a.z*b)
end

Then:

julia> T3(1,1,1) .* 5
T3{Int64}(5, 5, 5)

Notice that

julia> 5 .* T3(1,1,1)
ERROR: MethodError: no method matching length(::T3{Int64})
...

The overloaded method is not commutative. If you also want commutativity you also need to overload the reversed case:

function Broadcast.broadcasted(::typeof(*), a, b::T3{T}) where T
    return b .* a
end

Then

julia> 5 .* T3(1,1,1)
T3{Int64}(5, 5, 5)

While I think the accepted answer is the most practical, this is the answer to do exactly what OP asked to (overload a broadcasted operator).

1 Like