Macro to create struct according to existing variables' type and name

Hello everyone,
I was wondering if there was a way to define a macro @define that, given

let
a::Float64 = 0.5
b::Int64 = 1
@define MyStruct a b
end

would define a struct MyStruct with fields a::Float64 and b::Int64, as much as the following code:

let
a::Float64 = 0.5
b::Int64 = 1
struct MyStruct
a::typeof(a)
b::typeof(b)
end
end

This would define a MyStruct which types are correctly represented as svec(Float64, Int64).
My naive approach to the macro is of text replacement, but it doesn’t work, as only the Symbols a and b are passed in, without any information about their types. The result is that the types of a defined struct always look like svec(Any, Any)

macro define(name, variables...)
    local variables_with_types = []
    for i = 1 : length(variables)
        local current_var_with_type = Symbol("$(variables[i])::typeof($(variables[i]))")
        push!(variables_with_types, current_var_with_type)
    end

    local struct_definition =  quote 
        struct $name
            $(variables_with_types...)
        end
    end

    return esc(:($struct_definition))
end

Is there a way to achieve such task? Or must the macro be written in the form @define MyStruct (a, Float64) (b, Int64)?

No, and yes, in that order :slightly_smiling_face:

Macros operate on syntax, not on values. The fact that the macro only sees the symbols :a and :b is by design. That’s what macros do: they take syntax and turn it into other syntax.

You could, if you really want, create a macro that outputs a call to eval() to define the struct based on the actual values (and thus, types) of :a and :b in whatever scope that macro was called in but…I really wouldn’t recommend it. There’s almost certainly a cleaner way to achieve whatever your actual goal is.

Perhaps if you can explain more about what your goal with this macro is, perhaps we can help you find a simpler way of achieving it.

1 Like

You can use a parametric struct to achieve a similar effect:

julia> using QuickTypes

julia> @qstruct_fp MyStruct(a, b)

julia> MyStruct(0.5, 1)
MyStruct{Float64,Int64}(0.5, 1)
2 Likes

Thank you for the answer. I am currently developing a package where I would like to provide an interface that looks like this:

@define MyObject begin
    a::Int64 = 2
    b::Float64 = 1.0
    whatever_variable::Float64 = 2.3
    c::AnotherObject = AnotherObject(whatever_variable)
        
    @new(a, b, c)
end

which should expand to (or equivalent):

struct MyObject
    a::Int64
    b::Float64
    c::CustomStruct

    function MyObject()
        a::Int64 = 2
        b::Float64 = 1.0
        whatever_variable::Float64 = 2.3
        c::AnotherObject = AnotherObject(whatever_variable)
        return new(a, b, c)
    end
end

There is no special need for me to have the user to provide the name of the struct. So, if it easier, the interface could just look like: @define begin...end

Yes, that looks perfectly doable, since all of the information (i.e. the names and types of a and b) are contained within the input to the macro. In fact, it looks like what you’re trying to do is very close to what Parameters.jl already does: https://github.com/mauro3/Parameters.jl Does that package fit your needs?

It definitely looks close to what I need. But how could I go about having extra variables that are used for the initialization of others (e.g. the whatever_variable in my previous example), without including them in the struct? Also, is there a way for me to abstract the keyword struct away from the macro, so that the user can just write @define begin...end? Sorry for the amount of questions, I am still a learner in the macros world :slight_smile:

1 Like