Broadcast over struct fields?

For a struct with elements of the same type.
Is there a way to broadcast operations over the elements, and create a new struct.
Like you can for a Dictionary.

struct X
    a::Float64
    b::Float64
    c::Float64
end

x = X(1,2,3)
x.^2 #X(1,4,9)            a way to do this ?


d = Dictionary([:a,:b,:c],[1,2,3])
d.^2 #Dictionary([:a,:b,:c],[1,4,9])               like this

Couple ways:

X( ( (getfield.(Ref(x), 1:fieldcount(X) )) .^ 2)...)
X( ( (getfield.(Ref(x), fieldnames(X)   )) .^ 2)...)

Iā€™m not familiar with this dictionary type or this broadcasting behavior

Dictionaries.jl its pretty cool:)

1 Like

Do you want the same struct back, but with modified fields? This kind of operation is what so-called ā€œlensesā€ do, in Julia they are implemented in Accessors.jl composably and with a very nice interface.

Your specific example, squaring all properties in x:

julia> using Accessors

julia> x
X(1.0, 2.0, 3.0)

julia> @modify(v -> v^2, x |> Properties())
X(1.0, 4.0, 9.0)
2 Likes

Thanks.
I wasnā€™t aware of Accessors.jl
Thatā€™s a nice ability to modify existing or create a new struct.

Can the below approach for multiplication be extended to more operations without writing each individually ?

import Base.*

struct X
    a::Float64
    b::Float64
    c::Float64
end

X(x::Vector)=X(x...)
((;a,b,c)::X)() =  [a,b,c]
*(y::Number,x::X) = y * x() |> X
*(x::X,y::Number) = y * x() |> X



julia> 10X(1,2,3)
X(10.0, 20.0, 30.0)

I donā€™t think itā€™s possible to write a definition for multiple operations at once because functions arenā€™t grouped together in any way besides subtyping Function, and you are not allowed to annotate a called instance with Function in a method definition. If thatā€™s not true or thereā€™s a workaround, someone please correct me.

If the definitionsā€™ expressions share a structure, you can write them in a looped $-interpolated eval instead of repeatedly pasting and tweaking code, hereā€™s the Code Generation docs about it.

Accessors always create a new struct: itā€™s cleaner and often more performant.

If you need basic vector arithmetic, consider using or subtyping StaticArrays.FieldVector.

2 Likes

Depending what your original problem is, you might want to use LabelledArrays
(GitHub - SciML/LabelledArrays.jl: Arrays which also have a label for each element for easy scientific machine learning (SciML)) rather than your own struct.

1 Like

Legend! - just what I needed

import StaticArrays.FieldVector

struct Point3D <: FieldVector{3, Float64} 
    x::Float64
    y::Float64
    z::Float64
end

A = Point3D(1,2,3)

A *= 2

A.z

This is a ā€œtechniqueā€ Iā€™ve seen used in some library function to override a group of functions.

import Base.*, Base.+,Base.-,Base./

struct X
    a::Float64
    b::Float64
    c::Float64
end

X(x::Vector)=X(x...)
((;a,b,c)::X)() =  [a,b,c]
julia> for o in (:*,:+,:-,:/)
           @eval $o(y::Number,x::X) = ($o).(y, x()) |> X
           @eval $o(x::X,y::Number) = ($o).(x(), y) |> X
       end

julia>  X(12,24,30)/3
X(4.0, 8.0, 10.0)

julia>  1/3*X(12,24,30)
X(4.0, 8.0, 10.0)

julia>  X(1,2,3)-3
X(-2.0, -1.0, 0.0)

julia>  -3+ X(1,2,3)
X(-2.0, -1.0, 0.0)

julia> 10 - X(1,2,3)
X(9.0, 8.0, 7.0)

The main reason for my post (rather than suggesting an alternative solution to those already provided) is to ask for enlightenment on the logic of the expression ((;a,b,c)::X)() = [a,b,c]

I understand how it is used operationally but I would like to understand the meaning of the syntax (starting from the use of the ā€˜;ā€™) . Is it a function? what kind of function? or what else is it?

1 Like

This looks like ā€œArgument Destructuringā€ Functions Ā· The Julia Language
Basically just a shorthand for writing out X.a, X.b, etc. the function should be identical to

(x::X)() =  [x.a,x.b,x.c]

And the whole expression is turning X into a ā€œcallable typeā€: Methods Ā· The Julia Language

1 Like

Thank you very much.
I add some links related to the many ways to define methods of functions.

1 Like