# Macro to crate a struct and its external constructor

**URL:** <https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565>\
**Category:** General Usage\
**Tags:** metaprogramming\
**Created:** [September 6, 2023, 10:12am UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565 "2023-09-06T10:12:22Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![CrisCool](https://avatars.discourse-cdn.com/v4/letter/c/977dab/32.png) [@CrisCool](https://discourse.julialang.org/u/CrisCool)\
**Post date:** [September 6, 2023, 10:12am UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565/1 "2023-09-06T10:12:22Z")

</div>

Hi there. I’m new to metaprogramming and I’m running into trouble when trying to create a macro for replacing some bolier plate code. The code creates a series of structs and their corresponding external constructors.  
One example for the struct and the constructor is below:

```julia
struct FnaP <: FastaPaths
    p::String
    ext::String
end

function FnaP(p::String)::FnaP
    ext = getFileExtention(p)
    if ispresent(ext, CLM_General.ALLOWED_EXT["FnaP"])
        return FnaP(p, ext)
    else    
        error("The file '$p' is not a valid fasta nucleic acid file.")
    end
end

```

My code above creates three methods for the FnaP constructor, which accept different types of inputs:

```julia
FnaP(::String)
FnaP(::String, ::String)
FnaP(::Any, ::Any)

```

Because I need to declare maye similar structs and corresponding constructors, I’ve created a macro as follows:

```julia
macro definePathDT(name::Symbol)#, allowed_ext::String)
    quote
        struct $name <: PathsDT
            p::String
            ext::String
        end

        function $name(p::String)
            ext = getFileExtention(p)
            if ispresent(ext, ALLOWED_EXT[$(string(name))]) #CLM_General.
                return $name(p, ext)
            else    
                error("The file '\$p' is not a valid $(string(name)) file.")
            end
        end
    end
end

```

When I call the macro with

```julia
@definePathDT FnaP

```

I only get two methods for my FnaP constructor, both corresponding to the inner constructor:

```julia
FnaP(::String, ::String)
FnaP(::Any, ::Any)

```

Basically, I don’t get the external constructor.

What am I doing wrong?

---

<div class="post-metadata">

**Author:** ![skleinbo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/skleinbo/32/36080_2.png) [@skleinbo](https://discourse.julialang.org/u/skleinbo)\
**Post date:** [September 6, 2023, 10:31am UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565/2 "2023-09-06T10:31:22Z")

</div>

`@macroexpand` is very helpful in debugging macro code. Invoking your macro yields

```julia
julia> @macroexpand @definePathDT FnaP
quote
    #= REPL[1]:3 =#
    struct FnaP <: Main.PathsDT
        #= REPL[1]:4 =#
        p::Main.String
        #= REPL[1]:5 =#
        ext::Main.String
    end
    #= REPL[1]:8 =#
    function var"#41#FnaP"(var"#43#p"::Main.String)
        #= REPL[1]:8 =#
        #= REPL[1]:9 =#
        var"#42#ext" = Main.getFileExtention(var"#43#p")
        #= REPL[1]:10 =#
        if Main.ispresent(var"#42#ext", Main.ALLOWED_EXT["FnaP"])
            #= REPL[1]:11 =#
            return var"#41#FnaP"(var"#43#p", var"#42#ext")
        else
            #= REPL[1]:13 =#
            Main.error("The file '\$p' is not a valid $(Main.string(Main.name)) file.")
        end
    end
end

```

Look at the mangled variable names. The macro [hygiene pass](https://docs.julialang.org/en/v1/manual/metaprogramming/#Hygiene) has converted all function and variable names into generic ones to avoid clashing with existing ones, e.g. a function `$name` might already exist in the evaluating module.  
This shouldn’t pose a problem here.

To prevent macro hygiene, enclose the returned expression with `esc(...)`.

```julia
macro definePathDT(name::Symbol)#, allowed_ext::String)
    esc(quote
        struct $name <: PathsDT
            p::String
            ext::String
        end

        function $name(p::String)
            ext = getFileExtention(p)
            if ispresent(ext, ALLOWED_EXT[$(string(name))]) #CLM_General.
                return $name(p, ext)
            else    
                error("The file '\$p' is not a valid $(string(name)) file.")
            end
        end
    end)
end

```

```julia
julia> abstract type PathsDT end

julia> @definePathDT FnaP
FnaP

julia> methods(FnaP)
# 3 methods for type constructor:
 [1] FnaP(p::String)
     @ REPL[6]:8
 [2] FnaP(p::String, ext::String)
     @ REPL[6]:4
 [3] FnaP(p, ext)
     @ REPL[6]:4

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 6, 2023, 12:03pm UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565/3 "2023-09-06T12:03:38Z")

</div>

This is what the escaped expression looks like. The other effect of escaping besides changing some variable names (the docs call them “local” to the expression but the criteria isn’t very clear there) is that function calls are not taken from the macro definition’s scope anymore; the `Main.` are gone so the symbols will instead take from the macro call scope. This is as close to pasting code as it gets, and if some symbols are intended to belong to the macro definition scope, it’s just a matter of writing multiple `esc` at the other subexpressions (there is no unescaping).

```julia
julia> @macroexpand @definePathDT FnaP # the esc version
quote
    #= REPL[16]:3 =#
    struct FnaP <: PathsDT
        #= REPL[16]:4 =#
        p::String
        #= REPL[16]:5 =#
        ext::String
    end
    #= REPL[16]:8 =#
    function FnaP(p::String)
        #= REPL[16]:8 =#
        #= REPL[16]:9 =#
        ext = getFileExtention(p)
        #= REPL[16]:10 =#
        if ispresent(ext, ALLOWED_EXT["FnaP"])
            #= REPL[16]:11 =#
            return FnaP(p, ext)
        else
            #= REPL[16]:13 =#
            error("The file '\$p' is not a valid $(string(name)) file.")
        end
    end
end

```

---

<div class="post-metadata">

**Author:** ![CrisCool](https://avatars.discourse-cdn.com/v4/letter/c/977dab/32.png) [@CrisCool](https://discourse.julialang.org/u/CrisCool)\
**Post date:** [September 6, 2023, 12:16pm UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565/4 "2023-09-06T12:16:42Z")

</div>

Thanks @skleinbo and @Benny. I’ve used the esc() and now it works as intended. I have a further question now.  
How can I write a for loop to use the `@definePathDT` to generate multiple Data Types.

Basically, I can write:

```julia
@definePathDT FnaP
@definePathDT FaaP
@definePathDT FastaQP
@definePathDT BamP
@definePathDT TableP

```

However, instead, I would like to use a for loop. But anytime I’m doing something like:

```julia
mynames = [:Fna, :Faa]
for i in 1:eachindex(mynames)
  @definePathDT mynames[i]
end

```

I get an error:

```julia
LoadError: MethodError: no method matching var"@definePathDT"(::LineNumberNode, ::Module, ::Expr)

Closest candidates are:
  var"@definePathDT"(::LineNumberNode, ::Module, ::Symbol)

```

Any idea?  
Thanks

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [September 6, 2023, 12:27pm UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565/5 "2023-09-06T12:27:54Z")

</div>

Macros are not like function calls that happen at runtime, they transform the code as Julia reads it, before it’s actually run. So what `@definePathDT mynames[i]` is working on isn’t `Fna` or `Faa`, it’s the expression `mynames[i]`. That error means that your `definePathDT(::Symbol)` macro method can’t work on `::Expr`essions, but even if you evade that error, `mynames[i]` makes no sense as an input e.g. `struct mynames[i] <: PathsDT`.

So, we need to construct an expression with a macro call at runtime and use a function call to evaluate it at runtime.

```julia
for i in eachindex(mynames) # fixed eachindex typo
  eval( :( @definePathDT $(mynames[i]) ) )
end

```

Funnily enough there’s a macro to save writing out the quote delimiters, but it just expands to the runtime function call above. Bear in mind that `@eval` was designed to handle `$` as if it were quote interpolation, `$` is not generally something that works in macro expressions.

```julia
for i in eachindex(mynames) # fixed eachindex typo
  @eval @definePathDT $(mynames[i])
end

```

I’m sure you can also put together that you didn’t need a macro at all, you could’ve `eval`ed the big quote in a loop, where you won’t have to consider macro hygiene and variable mangling. This pattern is explained in the section Code Generation of the Metaprogramming page of the Manual.

---

<div class="post-metadata">

**Author:** ![CrisCool](https://avatars.discourse-cdn.com/v4/letter/c/977dab/32.png) [@CrisCool](https://discourse.julialang.org/u/CrisCool)\
**Post date:** [September 6, 2023, 2:49pm UTC](https://discourse.julialang.org/t/macro-to-crate-a-struct-and-its-external-constructor/103565/6 "2023-09-06T14:49:53Z")

</div>

@Benny yes you are right, I don’t need to create a macro for generating boiler plate code. It seems like I was using a very round about way to solve problem.  
As you suggested, I used directly eval() function and my code looks like this now:

```julia
for sym in [:FnaP, :FaaP, :FastaQP, :BamP, :TableP]
    eval(quote
        struct $sym <: PathsDT
            p::String
            ext::String
        end

        function $sym(p::String)
            ext = getFileExtention(p)
            if ispresent(ext, ALLOWED_EXT[$(string(sym))]) #CLM_General.
                return $sym(p, ext)
            else  
                error("The file '\$p' with extention '\$ext' is not a valid $(string($sym)) file.")
            end
        end
    end)
end

```

It works quite fine! The newly generated data types and constructors can be exported and used from other modules, like regular code.

Thank you both for helping me wrap up my head around metaprograming.
