Boilerplate code generation by evaluation of parsed strings


#1

Hi all,

The following is a trivial MWE of something I find myself doing sometimes in my code: I define some new struct

struct M
    x::Float64
    y::String
end

and I want to build accessor functions for getting the fields from an instance of the type. Rather than two lines of code, I use one line, exploiting common features of both functions:

[ eval(parse("get$(fn)(m::M) = m.$(fn)")) for fn in ["x", "y"] ]

Two questions (from someone who doesn’t really understand meta-programming):

1) Is there anything wrong with writing code like this? My understanding is that it is both safe and performant, since I’m not evaling any user generated strings, just my own code (so it is safe), and all the evaling is happening at compile time (so it is performant). Is this understanding correct?

2) The examples from the docs (e.g. the section on code-generation) all tend to use the @eval macro for code generation, rather than parsing and evaling strings like I have done above. I can’t for the life of me see how I would write the above code using the @eval macro (but I really am new to this stuff). The main issue is that I’m not extending existing function names, so I feel like I need to use the string interpolation trick in order to get the function names that I want and simultaneously reference the fields that I’m interested in, but no doubt there is a simple and obvious way around this issue. Could anyone re-write my above MWE but using the @eval macro? That might be all I need to get past this mental obstacle (and hopefully I’ll be able to generalise to more complicated real-world use-cases).

Thanks in advance to all responders,

Colin


#2

It’s safe but it’s a bad habbit that can lead to unsafe code in the future.
If you treat the evaluation of the whole thing as compile time then there won’t be any code that has any performance issue. If you don’t treat the whole code as compile time than no it’s not performant.

@eval is equivalent to eval

Correct. Parsing a string should never be done for metaprogramming. Except when you need to workaround parser difference between julia versions (though I wouldn’t call this usecase metaprogramming…).

Correct, you do need string interpolation, but you do not need to and should not parse the interpolated string.

@eval $(Symbol("get$fn"))(m::M) = m.$fn

As a side note, don’t use comprehension as loops.


#3

Wonderful and comprehensive answer, thank you very much! One quick question:

Could you elaborate on this? I thought a comprehension was just a shorthand notation for a loop…

Thanks again.

Colin


#4

It creates an array of useless results. Also, it’s really not much shorter than for x in y; f(x); end


#5

Understood. Thank you.

Colin