Using metaprogramming to define a bunch of methods at once

I’m trying to replicate the following trick that I found in SpecialFunctions.jl/src/bessel.jl:

## Bessel functions
# besselj0, besselj1, bessely0, bessely1
for jy in ("j","y"), nu in (0,1)
    jynu = Expr(:quote, Symbol(jy,nu))
    jynuf = Expr(:quote, Symbol(jy,nu,"f"))
    bjynu = Symbol("bessel",jy,nu)
    if jy == "y"
        @eval begin
            $bjynu(x::Float64) = nan_dom_err(ccall(($jynu,libm),  Float64, (Float64,), x), x)
            $bjynu(x::Float32) = nan_dom_err(ccall(($jynuf,libm), Float32, (Float32,), x), x)
            $bjynu(x::Float16) = Float16($bjynu(Float32(x)))
        end
    else
        @eval begin
            $bjynu(x::Float64) = ccall(($jynu,libm),  Float64, (Float64,), x)
            $bjynu(x::Float32) = ccall(($jynuf,libm), Float32, (Float32,), x)
            $bjynu(x::Float16) = Float16($bjynu(Float32(x)))
        end
    end
    @eval begin
        $bjynu(x::Real) = $bjynu(float(x))
        $bjynu(x::Complex) = $(Symbol("bessel",jy))($nu,x)
    end
end

Here’s the code snippet that gives my MWE:

const Phases = @enum solid liquid vapor

struct CpData1{T}
    Tmin::T
    Tmax::T
    a::T
    b::T
    c::T
    d::T
    phase::Phases
end

function minimumtemp(dat::CpData1, T) end
function maximumtemp(dat::CpData1, T) end

for aphase in [:solid, :liquid, :vapor] 
    for acond in [:maximum, :minimum]
        @eval begin
            fname1 = Symbol(acond, "temp_", aphase) 
            fname2 = Symbol(acond, "temp")
            $fname1(dat::MultiCp, T) = $fname2(dat, dat.phaseranges[Phases::$aphase])
        end
    end
end

This gives the following errors:

UndefVarError: CpData1 not defined and UndefVarError: fname1 not defined.

I have a few questions about this. First, does the first error mean that I can’t use a struct defined in the same file because @eval executes before Julia has executed the struct definition? Second, I don’t understand the fname error at all because I think I’m using fname1 EXACTLY the same way that bjynu is being used in SpecialFunctions.jl. What am I missing here?

Thanks!

Looks like Phases is not a julia Type so CpData1{T} is not a valid definition. Secondly, you interpolate fname1 and fname2 even though you do not have these defined yet. Third, you cannot define a name to be both a value and a function simultaneously, which you are attempting with the names of fname1 and fname2. Instead, the symbol definitions must happen outside of the @eval statement so that they can be interpolated.

const Phases = @enum solid liquid vapor

struct CpData1{T}
    Tmin::T
    Tmax::T
    a::T
    b::T
    c::T
    d::T
    phase
end

function minimumtemp(dat::CpData1, T) end
function maximumtemp(dat::CpData1, T) end

for aphase in [:solid, :liquid, :vapor] 
    for acond in [:maximum, :minimum]
        fname1 = Symbol(acond, "temp_", aphase) 
        fname2 = Symbol(acond, "temp")
        @eval $fname1(dat::MultiCp, T) = $fname2(dat, dat.phaseranges[Phases::$aphase])
    end
end

Also, MultiCp is not defined.

1 Like

I think this:

Instead, the symbol definitions must happen outside of the @eval statement so that they can be interpolated.

was the real key for me, though I guess I was confused about Enums as well (I actually thought every enum was its own type for some reason). After making the edits you suggested, it seems to work great! Thanks.

MultiCp exists in the original file; I just forgot to add it when extracting my MWE code.