I need help (again) with macros. This was driving me crazy today, although I found quite a few “related” posts, nothing worked to achieve what I want so I start to think that I am working on a horribly bad design. I decided to ask you and hope for some insights.
I am dealing with tons of dynamically generated structs and parser functions and one of the main difficulties at the moment is to get different versions and corresponding parser functions “in-sync”. There are of course hard-coded solutions but I am sure Julia can do much better.
What I came up with is something like this:
abstract type StreamedObject end
import Base: getindex
function Base.getindex(f::T, s::Symbol) where T <: StreamedObject
haskey(f.data, s) && return f.data[s]
error("type $(typeof(f)) has no key named $(s)")
end
and then I need to create types with different versions during runtime. For this, I first create basic “container” types with the desired names.
struct Foo{V} <: StreamedObject
data::Dict{Symbol, Any}
end
struct Bar{V} <: StreamedObject
data::Dict{Symbol, Any}
end
Then it’s quite straight forward to create the streamer implementation for all kinds of different versions.
function stream(::Type{T}) where T<:Foo{1}
fields = Dict{Symbol, Any}()
fields[:a] = 1
fields[:b] = 2
fields[:c] = 3
T(fields)
end
function stream(::Type{T}) where T<:Foo{2}
fields = Dict{Symbol, Any}()
fields[:x] = 10
fields[:y] = 20
T(fields)
end
function stream(::Type{T}) where T<:Bar{23}
fields = Dict{Symbol, Any}()
fields[:q] = 10000
T(fields)
end
This is how it works (it’s really oversimplified but shows the concept):
julia> stream(Foo{1})
Foo{1}(Dict{Symbol,Any}(:a => 1,:b => 2,:c => 3))
julia> stream(Bar{23})
Bar{23}(Dict{Symbol,Any}(:q => 10000))
julia> stream(Bar{23})[:q]
10000
julia> stream(Bar{5})
ERROR: MethodError: no method matching stream(::Type{Bar{5}})
So the procedure to define a new type is currently:
-
Create the base type definition (this is the same for every type)
struct TypeName{V} <: StreamedObject data::Dict{Symbol, Any} end
-
Define the
stream(::TypeName{X})
method for each version
So far so good. I am aware that the performance is quite rubbish but at this moment I think this will not be a huge problem since it’s only for the high-level part.
My current problems
I totally failed to create structs from variables inside functions, in fact the only way I managed to achieve something was using eval
and I am still not sure if this is the way to go.
What I am basically facing is a function which runs and collects some information about struct definitions and I need to create the struct definitions and parse methods inside it, so that those become available inside the module.
Here is a dummy session which I am trying to get working:
function create_parsers()
for parser_name in ["ParserA", "ParserB"]
@initialise parser_name
end
end
and the non-working macro @initialise
(apologies, I have thousands of versions, I just paste here one of them):
macro initialise(streamer)
streamer_name = Symbol(eval(streamer))
quote
struct $(esc(streamer_name)) <: StreamedObject
data::Dict{Symbol, Any}
end
end
end
This works when I pass in a variable name:
julia> name = "ParserZ"
"ParserZ"
julia> @initialise name
julia> fieldtypes(ParserZ)
(Dict{Symbol,Any},)
julia> fieldnames(ParserZ)
(:data,)
But of course fails when trying to use it inside a function since it first does the macro expand:
julia> function create_parsers()
for parser_name in ["ParserA", "ParserB"]
@initialise parser_name
end
end
ERROR: LoadError: UndefVarError: parser_name not defined
Any ideas? I am really questioning my approach but I am sure Julia can provide some nice ways to solve this design problem easily.