Recently i’m have encountered an issue where i needed two packages. Package A
exports a bunch of useful methods from some type T
(also exported from A
), and another package B
that exported additional methods and a equivalent type S
(meaning we can convert from T
to S
). The problem is that the methods from package A
only accepts arguments with type T
and not S
. The most obvious solution is to explicit convert each object of type S
to T
before passing it to the methods. This can be very annoying some times, and another solution is to reimplement these methods for type S
. Now the problem is if the number of methods exported by package A
is very large, which can make the second solution unfeasible. To address this issue, i created a macro that automatically reimplement these methods for a specific type. Basicly, swapping the types in the arguments of each method and converting it in the function body before passing it to the method. This works well for now, but i’m pretty sure that there’s something wrong with it, and maybe in some situations it will break. Is there more efficients ways to accomplish the same thing, or nor even do this in the first place?
Here is the code for the macro (i have no idea how to name this thing):
macro remethods(_module, type1, type2)
quote
for name in names($_module)
if isdefined($_module, name)
obj = getfield($_module, name)
if obj isa Function
new_methods = []
fmethods = filter(m -> hasfield(typeof(m.sig), :types), methods(obj))
for method in fmethods
ftype, arg_types... = method.sig.types
name = method.name
arg_types = collect(arg_types)
indices = findall(==($type1), arg_types)
if !isempty(indices)
push!(new_methods, (; name, arg_types, indices ))
end
end
for method in new_methods
(; name, arg_types, indices) = method
foreach(i -> setindex!(arg_types, $type2, i) , indices)
arg_names = [gensym(:arg) for a in arg_types]
_args = [:($name::$T) for (name, T) in zip(arg_names, arg_types)]
_body = map(indices) do i
argname = arg_names[i]
:( $argname = convert($$type1, $argname) )
end
eval(:(
function $($_module).$(name)($(_args...); kwargs...)
$(_body...)
$($_module).$(name)($(arg_names...); kwargs...)
end
))
end
end
end
end
end |> esc
end
Simple example:
module A
export Person
export introduce
struct Person
first_name::String
last_name::String
end
introduce(p::Person) = println("Hi, my name is $(p.first_name) $(p.last_name)")
end # module A
Main.A
julia> module B
export Android
struct Android
name::String
model::String
end
end # module B~
Main.B
julia> using .A, .B
julia> Jhon = Person("John", "Connor")
Person("John", "Connor")
julia> Connor = Android("Connor", "RK800")
Android("Connor", "RK800")
julia> introduce(Jhon)
Hi, my name is John Connor
julia> introduce(Connor)
ERROR: MethodError: no method matching introduce(::Android)
Closest candidates are:
introduce(::Person) at REPL[2]:10
Stacktrace:
[1] top-level scope
@ REPL[8]:1
julia> Base.convert(::Type{Person}, a::Android) = Person(a.name, "")
julia> @remethods A A.Person B.Android
julia> introduce(Connor)
Hi, my name is Connor
The above is just a simple example, my original problem was to use Luxor with types from GeometryBasics.
Edit: just fixed some obvious mistakes in the macro