Problem evaluating symbol for macro inside function

Hey! I have been trying to make a saving macro to simplify my life and skip having to do the full saving syntax while keeping track of what I’m saving where. To do this, I have started writing a module. The main macro is as follows:

macro saving(funcname::String,params...)
	quote
		oldfile($funcname)
		h5write($funcname*".h5","paramsNames",stringSymbolSeparator($params))
		for (n,name) in enumerate(stringSymbolSeparator($params))
			h5write($funcname*".h5",name,eval($(esc(params))[n]))
			println("Saved $($(params)[n])")	
		end
	end
end

Where the oldfile function just makes sure that there isn’t any file saved with funcname (and moves it to a .old file if it exists), and stringSymbolSeparator takes “params” as symbols and returns an array where each entry is the text used to write the variables (so that if I run @saving "test" var1, it will save the variable var1 in “test.h5” HDF5 file under the name of “var1”). When I try running the macro with variables that I have saved in the global scope it seems to work fine, my problem is that if I run the macro inside of a function, the eval command seems to be evaluating within the wrong scope and I get an UndefVarError. I’m pretty sure that the workaround has something to do with the macro hygiene, but after looking for a while I just can’t seem to fix it.
Thanks a lot for your help :slight_smile:

1 Like

Using eval() inside a macro is almost never necessary, and it’s almost always a signal that something has gone wrong. In this case, there’s no need for eval at all.

Rather than tackling the full version of your code, here’s a simple example of saving a single variable. First, I defined a stub h5write function so I don’t actually have to generate hdf files for testing:

julia> function h5write(filename, data, label)
         println("saving: $data with label $label in file $filename")
       end
h5write (generic function with 1 method)

Then I built a macro to call that function. Note the use of esc(param) to get the un-escaped variable in the expanded code, and the use of QuoteNode to turn a variable into a symbol giving the name of that variable:

julia> macro saving(name, param)
         quote
           h5write(string($name, ".h5"), $(esc(param)), string($(QuoteNode(param))))
         end
       end
@saving (macro with 1 method)

We can verify that this works at local scope:

julia> let 
         local_var = [1, 2, 3]
         @saving "test" local_var
       end
saving: [1, 2, 3] with label local_var in file test.h5

and global scope:

julia> global_var = [1, 2]
2-element Array{Int64,1}:
 1
 2

julia> @saving "test" global_var
saving: [1, 2] with label global_var in file test.h5
8 Likes

Perfect, thanks this helps a lot.
I still had some problems for implementing a for loop inside the macro to run over a variable number of parameters. In the end I ended just doing one part in a macro, and another part in a function and I have to write two lines instead of one. Still, it works like it should now.
Thanks again!

Sorry to resurrect, but how would one deal with a loop?

If I wanted to iterate over a collection of variables I’d use something like this:

for obj in (obj1, obj2, obj3)
    @saving "filename" obj
end

but then all my “labels” would be obj instead of obj1, obj2, and obj3.

MWE:

julia> obj1 = rand(2);

julia> obj2 = "123";

julia> obj3 = b"456";

julia> for obj in (obj1, obj2, obj3)
           @saving "filename" obj
       end
saving: [0.6583616079535075, 0.02077698131163519] with label obj in file filename.h5
saving: 123 with label obj in file filename.h5
saving: UInt8[0x34, 0x35, 0x36] with label obj in file filename.h5

Never mind, I found out about Base.@locals, which solved this nicely!

1 Like