Search code examples
macrosjuliametaprogramming

Defining Julia types using macros


Let's say I want to define a series of structs that will be used as parametric types for some other struct later on. For instance, I would like to have something like

abstract type Letter end

struct A <: Letter end
struct B <: Letter end
...etc...

The idea I've had is to define a macro which takes a string of the name of the struct I want to create and defines it as well as some basic methods. I would then execute the macro in a loop for all names and get all my structs defined at compile time. Something like this:

const LETTERS = ["A","B","C","D"]
abstract type Letter end

macro _define_type(ex)
    lines = Vector{Expr}()
    push!(lines, Meta.parse("export $ex"))
    push!(lines, Meta.parse("struct $ex <: Letter end"))
    push!(lines, Meta.parse("string(::Type{$ex}) = \"$ex\""))
    return Expr(:block,lines...)
end

@_define_type "A"

for ex in LETTERS
    @_define_type ex
end

The first way of executing the macro (with a single string) works and does what I want. However, when I execute the macro in a loop it does not. It tells me some variables are declared both as local and global variables.

Can someone explain what is happening? I believe it may be solved by a proper use of esc, but I can't figure out how.

Thanks!

Edit: Thank you all for the amazing responses! Got my code running!


Solution

  • I think this is what you are trying to do:

    module MyModule
    
    abstract type Letter end
        
    const LETTERS = ["A", "B", "C", "D"]
    
    for letter in LETTERS
        sym = Symbol(letter)
        @eval begin
            export $sym
    
            struct $sym <: Letter end
            Base.string(::Type{$sym}) = $letter
        end
    end
    
    end
    
    using .MyModule
    
    string(A) # "A"
    

    See the Code Generation section for more details: https://docs.julialang.org/en/v1/manual/metaprogramming/#Code-Generation