# Module scoping and macro expansion

**URL:** <https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914>\
**Category:** General Usage\
**Tags:** macros, module\
**Created:** [January 13, 2023, 10:30am UTC](https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914 "2023-01-13T10:30:50Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![veddox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/veddox/32/45227_2.png) [@veddox](https://discourse.julialang.org/u/veddox)\
**Post date:** [January 13, 2023, 10:30am UTC](https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914/1 "2023-01-13T10:30:50Z")

</div>

So I’ve been getting really confused with the interaction between nested macro expansion and module scoping…

In my module `MyModel` I have a set of macros with which I implement a simple DSL. Things work fine in this simpler case:

```julia
julia> using MyModel

julia> @macroexpand @habitat(MyModel.@landcover() == MyModel.water)
quote
    function (pos, model)
        if MyModel.landcover(pos, model) == MyModel.water
            return true
        else
            return false
        end
    end
end

```

But in this more complex case, the function that `@habitat` generates suddenly has `(MyModel.pos, MyModel.model)` as function arguments, instead of just `(pos, model)`:

```julia
julia> @macroexpand MyModel.@initialise!(@habitat(MyModel.@landcover() == MyModel.water), pairs=true)
:(initialise! = MyModel.initpopulation(begin
              function (MyModel.pos, MyModel.model)
                  if MyModel.landcover(MyModel.pos, MyModel.model) == (MyModel.MyModel).water
                      return true
                  else
                      return false
                  end
              end
          end, $(Expr(:(=), Symbol("#527#pairs"), true))))

```

This throws an error if executed (`LoadError: syntax: "MyModel.pos" is not a valid function argument name`).

The definitions of the relevant macros are (note that `MyModel` exports `@habitat`, but not the other two):

```julia
macro initialise!(habitatdescriptor, kwargs...)
    :($(esc(:initialise!)) = initpopulation($habitatdescriptor, $(kwargs...)))
end

macro habitat(body)
    quote
        function($(esc(:pos)), $(esc(:model)))
            if $(esc(body))
                return true
            else
                return false
            end
        end
    end
end

macro landcover()
    :(landcover($(esc(:pos)), $(esc(:model))))
end

```

This is just one example of several similar problems I’m running into. So any help you can offer on this example would be great, but if you know of any good explanations on how macro expansion works in other module contexts, that would also be much appreciated!

---

<div class="post-metadata">

**Author:** ![veddox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/veddox/32/45227_2.png) [@veddox](https://discourse.julialang.org/u/veddox)\
**Post date:** [January 13, 2023, 11:45am UTC](https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914/2 "2023-01-13T11:45:45Z")

</div>

Basically the question is: how I can I stop Julia sticking `MyModel.` in front of every symbol that it sees in a nested macro?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [January 13, 2023, 12:19pm UTC](https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914/3 "2023-01-13T12:19:26Z")

</div>

> [@veddox](#):
>
> ```julia
> quote
> function($(esc(:pos)), $(esc(:model)))
> if $(esc(body))
> return true
> else
> return false
> end
> end
> end
> 
> ```

Usually it’s pretty rare to see macros that act on variables believed to exist in the current scope by the macro creator. In your case, you want `@landcover` to act on variables `pos` and `model` which have to exist already, otherwise the user will get a pretty obscure `UndefVarError` (they’d go looking where they’d written `pos` or `model` and not find anything).

But anyway, let’s assume you continue with that approach. In any code that is returned from a macro, symbols refer to things in the module containing the macro definition, _unless they’re escaped_. If they are escaped, they refer to things in the context where the macro is used.

> ```julia
> macro habitat(body)
> quote
> function($(esc(:pos)), $(esc(:model)))
> if $(esc(body))
> return true
> else
> return false
> end
> end
> end
> end
> 
> ```

This doesn’t work because in an anonymous function expression, the variable names in the definition are declared for use in the function body. What you’re trying to do is to make the function arguments be the same thing as the variables `pos` and `model` outside. It’s a bit confusing for me what you actually want to do here, because you’re not using `pos` and `model` in the function at all. If you want to close over them (refer to them in the body of the function) use them inside and escape them there. If you want to call the function with the variables, leave alone the input arguments and then separately call the function with the escaped variables.

Maybe this helps you as well [https://jkrumbiegel.com/pages/2021-06-07-macros-for-beginners/#whats-escaping](https://jkrumbiegel.com/pages/2021-06-07-macros-for-beginners/#whats-escaping)

---

<div class="post-metadata">

**Author:** ![veddox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/veddox/32/45227_2.png) [@veddox](https://discourse.julialang.org/u/veddox)\
**Post date:** [January 13, 2023, 5:18pm UTC](https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914/4 "2023-01-13T17:18:00Z")

</div>

Thank you for the quick response!

> Usually it’s pretty rare to see macros that act on variables believed to exist in the current scope by the macro creator.

Yes, I am aware of the risk. The reason for doing so is that `@landcover` should only ever be used in the context of a call to `@habitat`. (I am very clear about this in the documentation, and also plan to put in some coded safeguards later on to make sure it’s not being called anywhere else.)

`@habitat` constructs a function that tests whether a given position in the model landscape is suitable habitat for a species. It is meant to be used in conjunction with `@landcover` and a set of similar wrapper macros, which allow the user to quickly construct a conditional statement to define the habitat test. The idea is to enable something like this:

```julia
habitats = @habitat((@landcover() == grass || 
                         (@landcover() == agriculture && @croptype() != maize &&
                          @cropheight() < 10)) &&
                        @distanceto(forest) > 20)

```

The variables `pos` and `model` are needed by the underlying helper functions (e.g. `landcover()`), and therefore need to be passed to the habitat testing function, which simply passes them on. That’s why they appear in `@habitat`, but are not directly used there. That is also why it’s important that the symbols generated by `@landcover` match those generated by `@habitat`. Does that make sense?

So my big question is: _Why is the macroexpansion of `@habitat` different when it is called within another macro call (here: `@initialise!`), as compared to when it is called directly?`_

Thank you for the link to the macro tutorial, it seems like a very well-written introduction 🙂 I’ll take a more detailed look at it after the weekend.

---

<div class="post-metadata">

**Author:** ![veddox](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/veddox/32/45227_2.png) [@veddox](https://discourse.julialang.org/u/veddox)\
**Post date:** [January 21, 2023, 11:10am UTC](https://discourse.julialang.org/t/module-scoping-and-macro-expansion/92914/5 "2023-01-21T11:10:56Z")

</div>

OK, I’ve solved it - I just needed to do some more escaping in `@initialise!`:

Before:

```julia
macro initialise!(habitatdescriptor, kwargs...)
    :($(esc(:initialise!)) = initpopulation($habitatdescriptor, $(kwargs...)))
end

```

After:

```julia
macro initialise!(habitatdescriptor, kwargs...)
    :($(esc(:initialise!)) = initpopulation($(esc(habitatdescriptor)); $(map(esc, kwargs)...)))
end

```
