Passing all functions on object A to object B

Hello,

I have the type:

struct Car
   color::String
   model::String
   gears::Int
end

and I have another struct

struct CarGroup 
    cars::Vector{Car}
end

I would like all the functions called on Car to be applied to CarGroup in this way:

function color(c::Car)
return c.color
end

function color(cg::CarGroup)
return map(cg.cars) do c
           color(c)
end
end

Is this thing even possible with standard language? I imagine that I can define a macro that does and add it to all the functions of interest… but I would much prefer some magic function of the type:

 function f(cg::CarGroup, args...; kwargs...)
               map(c->f(c, args...;kwargs...), cg,cars)
end

In most cases, it’s enough to add one forwarding method per function. For example:

color(cg::CarGroup) = color.(cg.cars)

If you’d like to generate these wrappers automatically, you can use a small loop with @eval, e.g.::slightly_smiling_face:

for f in (:color, :paint)
    @eval $f(cg::CarGroup, args...; kwargs...) =
        map(c -> $f(c, args...; kwargs...), cg.cars)
end
3 Likes

No. The syntax for it does exist, but we’re deliberately prevented from defining one method for arbitrary callables because it silently interferes with other functions’ dispatch e.g. we really don’t want print(::CarGroup) calls to be affected:

julia> function (f::Function)(cg::CarGroup, args...; kwargs...)
           map(c->f(c, args...;kwargs...), cg,cars)
       end
ERROR: cannot add methods to builtin function `Function`
Stacktrace:
 [1] top-level scope
   @ REPL[6]:1

julia> function (f::Any)(cg::CarGroup, args...; kwargs...)
           map(c->f(c, args...;kwargs...), cg,cars)
       end
ERROR: cannot add methods to builtin function `Any`
Stacktrace:
 [1] top-level scope
   @ REPL[7]:1

julia> function (f::F)(cg::CarGroup, args...; kwargs...) where F
           map(c->f(c, args...;kwargs...), cg,cars)
       end
ERROR: function type in method definition is not a type
Stacktrace:
 [1] top-level scope
   @ REPL[5]:1

Best we can do is metaprogramming for select callables (see karei’s @eval loop), or embrace a higher order function that takes input callables. map(color, cg.cars) is already fairly terse, the only possible improvement is a custom higher order function that accesses the cars field for us like carmap(color, cg), which doesn’t save much writing.

Also note that it generally doesn’t really make sense to forward “all” functions in this manner.
For example, a recolor(car::Car)::Car function generalized to CarGroup should probably return CarGroup back, not a Vector{Car} like map would do.