For loop inside macro quote block


#1

I apologize that this question may not generalize to many other people’s problems but I’m having a lot of trouble coming up with a macro. Say for example, I have this dictionary

dict = Dict("a" => 1, "b" => Dict("inner" => "test"))

I want to create a macro getField(fields...) that generates a function for me depending on the number of arguments passed. For example, if one argument @getField "a" is passed it returns the function

x::Dict{String, Any} -> begin
  if !("a" in keys(x))
    return ""
  end
  return x["a"]
end

And if two arguments are passed @getField "b" "inner" then it returns the function

x::Dict{String, Any} -> begin
  if !("b" in keys(x))
    return ""
  end
  if !("inner" in keys(x["b"]))
    return ""
  end
  return x["b"]["inner"]
end

Basically, go through each layer of the dictionary and make sure the field is there and if it not there, return an empty string. My code so far that fails horribly is

macro getField(fields...)
    quote
        x::Dict{String, Any} -> begin
            if !($(fields[1]) in keys(x))
                return ""
            end
            tmp = $(fields[1])
            if $(length(fields)) > 1
                for f in $(fields[2:end])
                    if !(f in tmp)
                        return ""
                    end
                    tmp = tmp[f]
                end
            end
        end
    end
end

Can someone help me understand how to loop over the fields passed to create the function?


#2

This actually doesn’t need to be a macro. Instead, you can write a recursive function which will be easier to debug and maintain:

julia> function my_get_field(d, fields...)
         if first(fields) in keys(d)
           my_get_field(d[first(fields)], Base.tail(fields)...)
         else
           ""
         end
       end
my_get_field (generic function with 1 method)

julia> my_get_field(x) = x   # handle the case when no additional fields are left
my_get_field (generic function with 2 methods)

julia> dict = Dict("a" => 1, "b" => Dict("inner" => "test"))
Dict{String,Any} with 2 entries:
  "b" => Dict("inner"=>"test")
  "a" => 1

julia> my_get_field(dict, "a")
1

julia> my_get_field(dict, "b", "inner")
"test"

julia> my_get_field(dict, "b", "not here")
""

#3

Or, if you want something closer to your original implementation (but still with no need to write a macro):

julia> function my_get_field_2(fields...)
         x::Dict{String, Any} -> begin
           for field in fields
             if !(field in keys(x))
               return ""
             else
               x = x[field]
             end
           end
           return x
         end
       end
my_get_field_2 (generic function with 1 method)

julia> dict = Dict("a" => 1, "b" => Dict("inner" => "test"))
Dict{String,Any} with 2 entries:
  "b" => Dict("inner"=>"test")
  "a" => 1

julia> dict |> my_get_field_2("a")
1

julia> dict |> my_get_field_2("b", "inner")
"test"