Using splat inside macro

I wanted to create a macro to print simulation status every now and then.

function display_res(ntuple::NamedTuple)
    cur = Dates.format(now(), "HH:MM:SS dd.mm.yyyy");
    str = savename(ntuple, connector="; ", sort = false)

    println("Just finished: $str" * ". Time: $cur");
    return nothing;
end

macro display_res(vars...)
    quote 
        display_res($(:(@ntuple($(vars...)))))
    end 
end

savename and @ntuple are from DrWatson package. The former creates a string, the latter creates NamedTuple (e.g., n=5;g=5;@ntuple(n, g) would give (n=5, g=5).

Above code works in Repl, but when I put it in the module, it doesn’t, resulting in: UndefVarError: n not defined.

Following is the output from @macroexpand:

quote
    #= /path_to_file/file.jl:63 =#
    MyModuleName.display_res((n = MyModuleName.n, g = MyModuleName.g))
end

So, of course the problem is with MyModuleName.n and MyModuleName.g. Does anybody know how to cope with that? Cheers!

This isn’t really about splatting, it’s just the usual rules about macro hygiene: Metaprogramming · The Julia Language, namely that you need to escape (with esc()) values that you want to interpolate like this.

Here’s a simpler example that works. I’ve removed savename because I don’t have DrWatson, and I’ve also removed @ntuple because you can do the same thing via (; n, g) as of Julia 1.7:

function display_res(ntuple)
	println("got: ", ntuple)
end

macro display_res(vars...)
	quote
		ntuple = (; $(esc.(vars)...))
		display_res(ntuple)
	end
end

Usage:

julia> function foo()
         n = 1
         g = 2
         @display_res(n, g)
       end
foo (generic function with 1 method)

julia> foo()
got: (n = 1, g = 2)
3 Likes

As @rdeits underlined, recent versions of Julia make it more easy to build named tuples and keyword arguments. So if you do not need to support older Julia versions, you might want to consider getting rid of macros entirely. Something like that (again removing savename to make the example runnable without DrWatson):

function display_res(; kwargs...)
    ntuple = NamedTuple(kwargs)
    cur = Dates.format(now(), "HH:MM:SS dd.mm.yyyy");
    str = string(ntuple)

    println("Just finished: $str. Time: $cur");
    return nothing;
end
julia> let
           a = 2
           b = 3
           display_res_new(; a, b)
       end
Just finished: (a = 2, b = 3). Time: 10:47:37 12.03.2022

The call syntax is perhaps a bit less user-friendly than what you wanted, but this way you don’t have to worry about topics like macro hygiene (which are arguably kind of hard to grasp).

3 Likes

Thank you @rdeits @ffevotte

I will just add that your codes work in Julia 1.6.4.