Macro to create struct problem

Hello for compatibility with other library I need to create a struct with names of the fields supplied via list of Strings

I tried sth like this

structName="example_struct"
keyss= ["a","b","c"]
symbolKeys= map(key->Symbol(key),keyss )

macro make_struct(struct_name, fields)
    esc(quote struct $struct_name
        $(fields...)
        end
    end)
end
println(@macroexpand @make_struct(structName,symbolKeys))
eval(@make_struct(structName,symbolKeys))

I based the code on Trouble understanding macros: creating a struct on the fly

Still I get error

 MethodError: no method matching iterate(::Symbol)

What am I doing wrong?

A macro takes in the expression it’s given. It knows nothing about what keys actually is. So you when you try to splat the $(fields...), you are splatting :symbolKeys, which is what’s passed to the macro.

In general you cannot automatically generate a struct like this with a macro. You can only give a macro expressions and return expressions. Nothing which actually acts on what the expression represents.

1 Like

In this case, you’re probably looking for plain eval (or @eval): this is the way to take values and generate (and evaluate) code.

struct_name = "example_struct"
fields = ["a","b","c"]

@eval begin
    struct $(Symbol(struct_name))
        $(Symbol.(fields)...)
    end
end
2 Likes

Can you describe your problem in a little more detail? We might be able to recommend a different approach.

Thank You all for help! As to describing problem in more detail I want to automate definitions of some functions for automatic differentiation (there will be limited number of them like 6-7 - but the code is very repetitive). All information (conff_params below) will be constant and manually provided in a code - known at compilation time it requires:

  1. define set of variables definitions and for each weather it is parameter,state,output or input; additionally I pass the kernel function (testKern below) that has the same arguments and is the function I want to differentiate (conff_params below)
  2. need to create struct that is inheriting from Lux.AbstractExplicitLayer (KernelAstr below)
  3. define function that call Enzyme.autodiff_deferred (testKernDeff below)
  4. define ChainRulesCore.rrule (ChainRulesCore.rrule(::typeof(calltestKern) below)
  5. define Lux.initialparameters (Lux.initialparameters below)
  6. define Lux.initialstates (Lux.initialstates below)
  7. define apply function of the layer ((l::KernelAstr)(x, ps, st::NamedTuple) below)
  8. define call function using CUDA macro (calltestKern below)

all steps can be done using information supplied in variables list list (conff_params below)

How variables are defined

@enum paramType Eparameter=1 Estate Einput Eoutput

conff_params= Dict(
                "A"=>(Einput
                    ,(rng,l)->()  
                    ,Duplicated
                     )
                ,"p"=>(Eparameter
                ,(rng,l)->CuArray(rand(rng,Float32, l.confA, l.confA, l.confA))
                ,Duplicated
                )
                ,"Aout"=>(Eoutput
                    ,(rng,l)->()  
                    ,Duplicated
                    )
                ,"Nxa"=>(
                    Estate
                    ,(rng,l)->Nx
                    ,Const
                    )
                ,"Nya"=>(
                    Estate
                    ,(rng,l)->Ny
                    ,Const
                    )
                ,"Nza"=>(
                    Estate
                    ,(rng,l)->Nz
                    ,Const
                    )
                )

example code before metaprogramming


#lux layers from http://lux.csail.mit.edu/dev/manual/interface/
struct KernelAstr<: Lux.AbstractExplicitLayer
    confA::Int
    Nxa::Int
    Nya::Int
    Nza::Int
end


function testKern(A, p, Aout,Nxa,Nya,Nza)
    #adding one bewcouse of padding
    x = (threadIdx().x + ((blockIdx().x - 1) * CUDA.blockDim_x())) + 1
    y = (threadIdx().y + ((blockIdx().y - 1) * CUDA.blockDim_y())) + 1
    z = (threadIdx().z + ((blockIdx().z - 1) * CUDA.blockDim_z())) + 1
    Aout[x, y, z] = A[x, y, z] *p[x, y, z] *p[x, y, z] *p[x, y, z] 
    
    return nothing
end

function testKernDeff( A, dA, p
    , dp, Aout
    , dAout,Nxa,Nya,Nza)
    Enzyme.autodiff_deferred(testKern, Const, Duplicated(A, dA), Duplicated(p, dp), Duplicated(Aout, dAout),Const(Nxa),Const(Nya),Const(Nza))
    return nothing
end

function calltestKern(A, p,Nxa,Nya,Nza)
    Aout = CUDA.zeros(Nx+totalPad, Ny+totalPad, Nz+totalPad ) 
    @cuda threads = threads blocks = blocks testKern( A, p,  Aout,Nxa,Nya,Nza)
    return Aout
end

aa=calltestKern(A, p,Nx,Ny,Nz)
maximum(aa)

# rrule for ChainRules.
function ChainRulesCore.rrule(::typeof(calltestKern), A, p,Nxa,Nya,Nza)
    Aout = calltestKern(A, p,Nxa,Nya,Nza)#CUDA.zeros(Nx+totalPad, Ny+totalPad, Nz+totalPad )
    function call_test_kernel1_pullback(dAout)
        # Allocate shadow memory.
        threads = (4, 4, 4)
        blocks = (2, 2, 2)
        dp = CUDA.ones(size(p))
        dA = CUDA.ones(size(A))
        @cuda threads = threads blocks = blocks testKernDeff( A, dA, p, dp, Aout, CuArray(collect(dAout)),Nxa,Nya,Nza)

        f̄ = NoTangent()
        x̄ = dA
        ȳ = dp
        
        return f̄, x̄, ȳ,NoTangent(),NoTangent(),NoTangent()
    end   
    return Aout, call_test_kernel1_pullback

end
#first testing
# ress=Zygote.jacobian(calltestKern,A, p ,Nx,Ny,Nz)
# typeof(ress)
# maximum(ress[1])
# maximum(ress[2])




function KernelA(confA::Int,Nxa,Nya,Nza)
    return KernelAstr(confA,Nxa,Nya,Nza)
end


function Lux.initialparameters(rng::AbstractRNG, l::KernelAstr)
    return (paramsA=CuArray(rand(rng,Float32, l.confA, l.confA, l.confA))
    ,paramsB = CuArray(rand(rng,Float32, l.Nxa, l.Nya, l.Nza)))
end

Lux.initialstates(::AbstractRNG, ::KernelAstr) = (Nxa=Nx,Nya=Ny,Nza=Nz)


# # But still recommened to define these
# Lux.parameterlength(l::KernelAstr) = l.out_dims * l.in_dims + l.out_dims

# Lux.statelength(::KernelAstr) = 0

function (l::KernelAstr)(x, ps, st::NamedTuple)
    return calltestKern(x, ps.paramsA,st.Nxa,st.Nya,st.Nza),st
end

Ok, in that case I think the answer from @ffevotte is what you are looking for. You can put an @eval inside a for loop if the code that you’re generating is repetitive. However, there is a tradeoff. I think for loops with @eval are harder to read than just writing the code out by hand. Also, this kind of thing confuses the VS Code linter, so you won’t get correct linting for other parts of the code that use the generated types and functions. (Maybe that will be fixed in the future, but it seems like a tricky issue to fix in the linter.)

See this section of the manual for a discussion of code generation:

https://docs.julialang.org/en/v1/manual/metaprogramming/#Code-Generation

1 Like