Metaprogramming first steps

# Hello, i have a struct and i want to get with a macro a class builder and
# constructor Foo, like this:


# i have

struct Foo
    a::Int64
    b::Int64
    c::Any
    d::String
    e::Union{Nothing,Foo}
    # ...
    function Foo(; a,b,c,d,e)
        return new(a,b,c,d,e)
    end
end


# i need

mutable struct FooBuilder
    a::Union{Nothing,Int64}
    b::Union{Nothing,Int64}
    c::Any
    d::Union{Nothing,String}
    e::Union{Nothing,Foo}
end

function Foo(builder::FooBuilder)
    return Foo(
        a = builder.a,
        b = builder.b,
        c = builder.c,
        d = builder.d,
        e = builder.e,
    )
end



# i do

macro builder(
        type::Symbol,
        builder::Symbol = Symbol(type, :Builder),
    )
    if @eval !isstructtype($type)
        error("`$type` is not struct type")
    end
    if @eval @isdefined($builder)
        error("`$builder` is defined")
    end
    wrap(T::Type) = nothing isa T ? T : Union{Nothing,T}
    unite(name::Symbol, T::Type) = :($name::$T)
    unite(value::Symbol, key::Symbol) = :($key=builder.$value)
    local T = @eval $type
    local fields = unite.(
        T |> fieldnames,
        T |> fieldtypes .|> wrap,
    )
    local params = unite.(
        T |> fieldnames,
        T |> fieldnames,
    )
    return quote
        mutable struct $builder
            $(fields...)
        end
        function $type(builder::$builder)
            return $type(
                $(params...)
            )
        end
    end
end

@macroexpand @builder Foo

# i get
begin
    mutable struct FooBuilder
        a::Union{Nothing, Int64}
        b::Union{Nothing, Int64}
        c::Any
        d::Union{Nothing, String}
        e::Union{Nothing, Foo}
    end
    function var"#30#Foo"(var"#36#builder"::Main.FooBuilder)
        return var"#30#Foo"(
            $(Expr(:(=), Symbol("#31#a"), :((var"#36#builder").a))),
            $(Expr(:(=), Symbol("#32#b"), :((var"#36#builder").b))),
            $(Expr(:(=), Symbol("#33#c"), :((var"#36#builder").c))),
            $(Expr(:(=), Symbol("#34#d"), :((var"#36#builder").d))),
            $(Expr(:(=), Symbol("#35#e"), :((var"#36#builder").e)))
        )
    end
end

# what am I doing wrong ?
# why i get var"#30#Foo" or #31#a instead of Foo or :a ?
# where is my function Foo(builder::Main.FooBuilder) ?

Perhaps getname($type) instead of $type where you build the function?

getname ?

This is about macro hygiene. e.g. here is a simple example:

julia> macro foo(ex)
           ex
       end
@foo (macro with 1 method)

julia> @macroexpand @foo x = 1
:(var"#64#x" = 1)

julia> macro foo(ex)
           esc(ex)
       end
@foo (macro with 1 method)

julia> @macroexpand @foo x = 1
:(x = 1)

Here is the relevant docs section on Hygiene: Metaprogramming · The Julia Language

2 Likes

спасибо