Help with macro

question

#1

I am trying to write a macro which would transform

@enum_and_dict(foo, foodict, "A" => A, "B" => B)

to

@enum foo A B
foodict = Dict("A" => A, "B" => B)

I have tried this:

macro enum_and_dict(enumname, dictvar, pairs...)
    function expr_second(expr)
        @assert expr.head ≡ :(=>) "$expr is not a Pair."
        esc(expr.args[2])
    end
    quote
        @enum($(esc(enumname)), $(map(expr_second, pairs)...))
        $(esc(dictvar)) = Dict($(pairs...))
    end
end

and the Dict part works fine, but for the first one I get a very complicated expression, and I don’t know why, or how to fix it.


#2

What do you mean you get a very complicated expression? You mean the expansion result of @enum itself? It’s working AFAICT.


#3

I see… it also expanded @enum. So it is indeed working as expected.

What’s the Julia equivalent of COMMON-LISP:MACROEXPAND-1, that would just expand the first level, not recursively?


#4

I don’t think this exists. If you want to look at the code, see the entrypoint julia-expand-macros in src/macroexpand.scm.


#5

Thanks, I opened an issue.


How to link to Github issues/PRs in Discourse?
#6

Using the macro above in another module, I found that is missing an esc. A correct MWE is

module Foo                      # minimal working example
macro enum_and_Dict(enumname, dictvar, pairs...)
    function expr_second(expr)
        @assert expr.head ≡ :(=>) "$expr is not a Pair."
        esc(expr.args[2])
    end
    quote
        @enum($(esc(enumname)), $(map(expr_second, pairs)...))
        $(esc(dictvar)) = Dict($(map(esc, pairs)...))
    end
end
module Bar                      # using the macro in a submodule
import ..@enum_and_Dict
@enum_and_Dict(BarEnum, bar_dict, "a"=>a)
end
end

Question: is it possible to simplify this code, or make it more idiomatic? Style hints are welcome, I am still learning Julia macros.


#7

How is:
@enum_and_dict(foo, foodict, "A" => A, "B" => B)
a worthy improvement over:
@enum foo A B; foodict = Dict("A" => A, "B" => B)
?

Is there an important gain from the proposed macro, other than making the code resemble LISP (and potentially harder to read by some people)?

I think that the idiomatic way would be to take some distance from LISP and not use a macro in this case.


#8

In my actual use case, the enums have about 15–25 values, and I have 4+ different enums. Think of something like

@enum_and_Dict(EmploymentCategories,
               employment_encodings,
               "AA" => unemployed,
               "D2" => disabled_other,
               ... # approx. 20 other lines
               )

Enumerating values twice is error prone and tedious. I don’t see any resemblance to LISP in the code above, the goal is to eliminate redundancy. Also, I am not sure how you can do this without macros (but I am interested in suggestions — macros are not the end here, just the means).


#9

You don’t need a new macro if you don’t mind starting with symbols (at the risk of annoying akis with somewhat cryptic code):

module M

function enum_and_dict(d::Dict{String,Symbol},ename)
    ds=([v for (k,v) in d]...)
    @eval @enum $ename $(ds...)
    dnew = Dict(k => getfield(M,v) for (k,v) in d)
end

d1 = Dict("A" => :A, "B" => :B)
d2 = enum_and_dict(d1,:MyEnum)
display(MyEnum)
display(d2)
end

Enum M.MyEnum:
B = 0
A = 1
Dict{String,M.MyEnum} with 2 entries:
  "B" => B
  "A" => A


#10

To my eyes, the resemblance is in the mixed arguments inside long parentheses (I may be biased though).

I don’t mind symbols (they are present eitherway). And not annoyed by @eval (definitely less cryptic than the proposed macro). It should be $(values(d)...) though.