I should mention there are several packages which make this easier:
Here’s a deluxe version of the macro I started above:
julia> macro extendsMine(ex)
# Make struct subtype AbstractMine
name = ex.args[2]
ex.args[2] = :($name <: AbstractMine)
# Add some base fields
fields = ex.args[3]
push!(fields.args, :(c::Int))
push!(fields.args, :(d::Float64))
get_c = esc(:get_c)
get_d = esc(:get_d)
quote
$ex
# Add some getter methods
$get_c(s::$name) = s.c
$get_d(s::$name) = s.d
end
end
@extendsMine (macro with 1 method)
julia> abstract type AbstractMine end
julia> @extendsMine struct MyA
a::Int
end
get_d (generic function with 1 method)
julia> @extendsMine struct MyB
b::Int
end
get_d (generic function with 2 methods)
julia> MyA(1,2,3)
MyA(1, 2, 3.0)
julia> MyB(1,2,3)
MyB(1, 2, 3.0)
julia> get_c(ans)
2
julia> fieldnames(MyA)
(:a, :c, :d)
julia> fieldnames(MyB)
(:b, :c, :d)
julia> methodswith(MyA)
[1] get_c(s::MyA) in Main at REPL[1]:17
[2] get_d(s::MyA) in Main at REPL[1]:18
julia> methodswith(MyB)
[1] get_c(s::MyB) in Main at REPL[1]:17
[2] get_d(s::MyB) in Main at REPL[1]:18
julia> get_c(MyA(1,2,3))
2
julia> get_d(MyB(1,2,3))
3.0
Let’s discuss how one figures this out.
The first step is turn the starting code into an expression object. Then we probe the head, the args, and then recursively descend until we have identified all the components that we want to manipulate.
julia> ex = :(struct MyA <: AbstractMine
a::Int
end)
:(struct MyA <: AbstractMine
#= REPL[15]:2 =#
a::Int
end)
julia> typeof(ex)
Expr
julia> ex.head
:struct
julia> ex.args
3-element Vector{Any}:
false
:(MyA <: AbstractMine)
quote
#= REPL[15]:2 =#
a::Int
end
julia> ex.args[2]
:(MyA <: AbstractMine)
julia> ex.args[2].head
:<:
julia> ex.args[2].args
2-element Vector{Any}:
:MyA
:AbstractMine
julia> ex.args[3]
quote
#= REPL[15]:2 =#
a::Int
end
julia> ex.args[3].head
:block
julia> ex.args[3].args
2-element Vector{Any}:
:(#= REPL[15]:2 =#)
:(a::Int)
julia> ex.args[3].args[2]
:(a::Int)
julia> ex.args[3].args[2].head
:(::)
julia> ex.args[3].args[2].args
2-element Vector{Any}:
:a
:Int
julia> supertype(MyA)
AbstractMine
julia> supertype(MyB)
AbstractMine
julia> subtypes(AbstractMine)
2-element Vector{Any}:
MyA
MyB
In this case, I wanted to make the subtyping part of the macro, so I needed to compare this a struct definition that is not subtyped.
julia> ex_simple = :(struct MyB
b::Int
end)
:(struct MyB
#= REPL[28]:2 =#
b::Int
end)
julia> ex_simple.args[2]
:MyB
julia> typeof(ex_simple.args[2])
Symbol
Armed with the knowledge obtained above, I see that we need to replace argument 2 with an Expr
, and then we need to push some additional expressions into the field block. Lastly I need to create a new quote block to add some new escaped expressions.
Have fun!