Access to struct fields in generated function


#1

Hi,

I am trying to wrap my skull around metaprogramming. Feels like a stiff skull.

In my little example, I want a function that can take any composite type, and do some manipulation (here printing) of each field. Here we go

using StaticArrays
@generated function insight(f)
    print("Generating insight.\n")
    e = quote end  
    for fi in fieldnames(f) 
        blk = quote 
            print(string(fi),"::",string(typeof(f.$fi))," → ",show(f.$fi),"\n")
        end
        append!(e.args, blk.args)
    end
    return e
end

struct typo{nx}
    σ::SMatrix{nx,nx}
    b
    c
end

nx = 3

i = typo{nx}(randn(nx,nx),π,3.)
insight(i)

I get "undefVarError: fi not defined."
I know a “generated function” has access to type info, not values, at … generation time. I reckon fieldnames should be fair game. Are the fieldnames known at generation time? Is that the cause of the error anyway?

I also welcome comments on the way I append stuff. Is there simpler?

Philippe


#2

You need to think about what computation happens when:

print(string(fi),
      "::",string(typeof(f.$fi))," → ",
      show(f.$fi),"\n")

The first string(fi) is quoted, so it will be evaluated when the (generated) function is called. fi is not a variable in your generated function, so it’s an error. You probably want $(string(fi)) to evaluate it at generation-time.

The second string should work fine. But it will be performing the computation at runtime, since only $fi will be evaluated at generation time. You could use $(string(fieldtype(f, fi))) to do it at compile-time (though note that it’s not quite the same computation — your version shows the type of the value stored, but my version shows the type of the field)

The third string is fine.


#3

Thank you, that helps! One way to put it is:

@generate function foo(data)
generation time code
return quote 
    runtime code $(generation time code)
end

My code now works

using StaticArrays
@generated function insight(f)
    print("Generating insight.\n")
    e = quote end  
    for fi in fieldnames(f) 
        blk = quote 
            print($(string(fi))," :: ",$(string(fieldtype(f,fi)))," → ",string(f.$fi),"\n")
        end
        append!(e.args, blk.args)
    end
    return e
end

#4

Just a minor correction:

@generated function foo(data)
    runtime code
    return quote 
        runtime code $(generation time code)
    end
end

should instead be

@generated function foo(data)
    generation time code
    return quote 
        runtime code $(generation time code)
    end
end

#5

Indeed! I corrected my post.