Bulding a NamedTuple / Dict / struct from variables without repetitions in the code

Very often, especially when prototyping, I compute different quantities, and I want all of them to be easily accessible. The easiest way is to return a named tuple:

function foo()
    x = 1
    y = "hello" 
    z = [x,2,3]
    # ... and more 
    (x=x,y=y,z=z)
end 

Is there some way or an existing macro that I can use to avoid repetitions in the return argument “x=x” , “y=y” , etc ? Usually the variable names are much longer, and this is prone to errors.

In relation to that, I also have this issue when saving variables on a file, for example:

using JLD2
x=123
y = 456
jldopen("somefile.jld2","w") do f 
    f["x"]=x
    write(f,"y",y)
end

I have to write “x” and “y” twice. With the @save macro I would not have this issue, but it’s often the case that I do not want to save the whole working environment.

Thanks for your help!

Thrown together really quickly:

macro returnnt(x...)
    ex = [:($(esc(z)) = $(esc(z))) for z in x]
    return :(return ($(ex...),))
end

function foo()
    x = 1
    y = "hello" 
    z = [x,2,3]
    # ... and more 
    @returnnt x y z
end 

I’m sure there yet be dragons lurking in the corners of this implementation, but as a proof of concept, hope it’s enough for you for a start.

4 Likes

Thaks a lot , it seems to do the job. I tried to define a macro, but I got lost in the elements that I needed to escape. I am probably asking too much now, but it would be cool to have something like this, as well…


my_values = @wishfulthinkingmacro begin 
    x = 1
    y = "hello" 
    z = [x,2,3]
end

using Testing
@test my_values.x  == 1 # etc

zero repetitions and even less prone to typos ! I would try to build it myself, but of course I am very open to any advice.

Well that was a fun diversion. Way more caveats on this one (!!), but it works for the simple case you provided:

julia> using MacroTools

julia> using MacroTools: combinedef, postwalk

julia> macro autoreturn(f)
    fparts = splitdef(f)
    fbody = block(fparts[:body])
    vars = Symbol[]
    postwalk(fbody) do ex
        if @capture(ex, return x_)
            @warn "Explicit return seen!"
        end
        if @capture(ex, x_Symbol = y_)
            push!(vars, x)
        end
        ex
    end
    retpairs = [:($z = $z) for z in vars]
    push!(fbody.args, :(return ($(retpairs...),)))
    fparts[:body] = esc(fbody)
    return combinedef(fparts)
end

julia> @autoreturn function foo()
    x = 1
    y = "hello"
    z = [x,2,3]
end

julia> foo()
(x = 1, y = "hello", z = [1, 2, 3])

Edit: minor clean-ups

4 Likes

Thanks a lot for your help and very quick response ! It seems to work well for testing and prototyping… and hopefully I will gain some useful insights about metaprogramming.