According to this, I’m trying to use macro for inheritance as @xiaodai suggested.
However, I’ve not figured out how exactly macro works
What I’m trying to do is basically “refering a struct’s fields from others”.
For this, here’s my example code:
abstract type AbstractABC end
mutable struct BaseABC <: AbstractABC
a
end
mutable struct MyABC <: AbstractABC
_abc
b
end
macro inherit(ABC)
ex = quote
function Base.getproperty(abc::ABC, prop::Symbol)
if prop in fieldnames(BaseABC) && !(prop in fieldnames(ABC))
return getfield(abc._abc, prop)
else
return getfield(abc, prop)
end
end
end
return ex
end
@inherit(MyABC)
function test_all()
baseabc = BaseABC(1)
myabc = MyABC(baseabc, 2)
println(myabc.a) # should be "1"
end
test_all()
But it raises an error: UndefVarError: ABC not defined (at line 14).
Questions:
Is macro a “compressed code”? I mean, macro probably runs at compile time as described here, then it seems “a function for automatic generation of codes” to me.
If so, what should I correct to fulfill my purpose at the above code?
What I was trying to do is to make a macro @inherit for any struct ABC (ABC <: AbstractABC such that @inherit ABC will make the struct ABC can refer any fields of BaseABC (BaseABC <: AbstractABC).
I would appreciate if you know a more convenient and Julian approach
The corrected code:
abstract type AbstractABC end
mutable struct BaseABC <: AbstractABC
a
end
mutable struct MyABC <: AbstractABC
_abc
b
end
macro inherit(ABC)
ex = quote
function Base.getproperty(abc::$ABC, prop::Symbol)
if prop in fieldnames(BaseABC) && !(prop in fieldnames($ABC))
return getfield(abc._abc, prop)
else
return getfield(abc, prop)
end
end
end
return ex
end
@inherit(MyABC)
function test_all()
baseabc = BaseABC(1)
myabc = MyABC(baseabc, 2)
println(myabc.a) # = 1
end
test_all()
The more julian approach is to simply not do this… generally, you will only define a small handful of types as subtypes of an abstract type. In that case you should just write out all of the fields explicitly (which also should be a small handful). If you have dozens of types or dozens of fields, that is code that needs refactoring.
abstract type AbstactX end
# an interface function to grab the :x field.
f(x::AbstractX) = x.x
struct X <: AbstractX
x::Int
end
struct Y <: AbstractX
x::Int
y::Float64
end
struct Z <: AbstractX
x::Int
z::Float64
end
# etc.
Yes, a 1-to-1 port from another language is hard. It is easier to think about the big picture of what the code is trying to accomplish and reimagine how to get to that goal from first principles. Usually you will find your result is clearer, faster, shorter, and simply better. For large code bases though, this process is hard and sometimes not worth it in the short term. If you want to DM me a link to your python code, maybe I can offer some directed advice.