# Defining a macro in a local scope?

**URL:** <https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148>\
**Category:** General Usage\
**Tags:** macros, scope, local\
**Created:** [December 15, 2021, 3:11pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148 "2021-12-15T15:11:17Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Mark\_Nahabedian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mark_nahabedian/32/16562_2.png) [@Mark\_Nahabedian](https://discourse.julialang.org/u/Mark_Nahabedian)\
**Post date:** [December 15, 2021, 3:11pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/1 "2021-12-15T15:11:17Z")

</div>

I’m trying to define a macro whose expansion references a local variable.

Here’s my example:

```julia
struct Reference
    source::Union{Nothing, LineNumberNode}
    registry::Dict
    key
end

let
    r = Dict()
    macro ref(name)
        Expr(:call, :Reference, __source__ , r, name)
    end
    @ref("foo")
end

```

I’d expect this to translate to

```julia
let
    r = Dict()
    Reference(<someLineNumberNode>, f, "foo")
end

```

but instead, I get the error

```julia
ERROR: syntax: macro definition not allowed inside a local scope
Stacktrace:
 [1] top-level scope
   @ none:1

```

In CommonLisp I could use MACROLET. Does Julia have anything analogous to this?

Thanks.

Why?

[GitHub - MarkNahabedian/AnotherParser.jl](https://github.com/MarkNahabedian/AnotherParser.jl) allows for a BNF to be constructed as a tree of structs and for that tree to drive the parsing of an input string.

The Dict represented by `r` maps non-terminals in a BNF grammar to the syntax associated with that non-terminal. The Reference struct allows for delayed, indirect substitution of a BNF subtree for a specified non-terminal `name`.

An example grammar that implements the SemVer version syntax is implemented in [AnotherParser.jl/test/SemVerBNF.jl at master · MarkNahabedian/AnotherParser.jl · GitHub](https://github.com/MarkNahabedian/AnotherParser.jl/blob/master/test/SemVerBNF.jl).

I can construct a working parser using my struct definitions, but the grammar is diccicult to debug because the structs are not tagged with their source locations.

I have a macro which, for each such struct definition, rewrites the struct to

```
add a `source` field;

rewrites its constructors to support that field;

defines a macro with the same name as the struct that invokes the constructor with __source__ filled in.

```

That macro is defined in [AnotherParser.jl/src/note\_BNFNode\_location.jl at master · MarkNahabedian/AnotherParser.jl · GitHub](https://github.com/MarkNahabedian/AnotherParser.jl/blob/master/src/note_BNFNode_location.jl)

The BNF node definitions themselves are defined in [AnotherParser.jl/src/BNFtypes.jl at master · MarkNahabedian/AnotherParser.jl · GitHub](https://github.com/MarkNahabedian/AnotherParser.jl/blob/master/src/BNFtypes.jl).

I could define a macro which takes the entire grammar and constructs the node tree from that. This would allow me to do a tree walk to replace the shorthand for all of my Reference nodes, but I don’t see how to avoid a tree walk that I fdeel the compiler should do for me were I allowed to define macros in a local scope.

---

<div class="post-metadata">

**Author:** ![woclass](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/woclass/32/212699_2.png) [@woclass](https://discourse.julialang.org/u/woclass)\
**Post date:** [December 15, 2021, 3:45pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/2 "2021-12-15T15:45:07Z")

</div>

> Macros in Julia are only allowed to be defined in the global scope.

> [@Lexically Scoped Macros?](https://discourse.julialang.org/t/lexically-scoped-macros/6835/2):
>
> I heard from someone [here](https://www.reddit.com/r/lisp/comments/7961z9/are_there_any_new_interesting_lisps_being/dp6hoav/) that Lexical macros were excluded for performance reasons as they make it “impossible to statically analyse the code and generate fast code”. This makes no sense to me as lexically scoping the macro should only serve to further localize its effects and should be converted to proper code at before compilation so I fail to see any problem.

What happens if you define this macro globally?

You can also try defining the macro in a new module.

---

<div class="post-metadata">

**Author:** ![Mark\_Nahabedian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mark_nahabedian/32/16562_2.png) [@Mark\_Nahabedian](https://discourse.julialang.org/u/Mark_Nahabedian)\
**Post date:** [December 15, 2021, 3:59pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/3 "2021-12-15T15:59:13Z")

</div>

Thanks for your quick response.

I don’t see how I can define the macro globally since it depends on a local variable that would not exist in the global context.

The local function that the macro is intended to replace is a convenience so that I don’t explicitly need to pass the local variable whose value is the Dict every time I call the Reference constructor. The purpose of the macro is to include a source location as well.

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [December 15, 2021, 4:24pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/4 "2021-12-15T16:24:28Z")

</div>

> [@Mark\_Nahabedian](#):
>
> The purpose of the macro is to include a source location as well.

In that case you can just use `LineNumberNode(@ __LINE__ , @ __FILE__ )` instead. (Note that the ` __source__ ` in your macro definition above will just end up evaluating to `nothing`. You probably wanted to put it inside a `QuoteNode`.)

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [December 15, 2021, 4:36pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/5 "2021-12-15T16:36:46Z")

</div>

> [@Mark\_Nahabedian](#):
>
> I don’t see how I can define the macro globally since it depends on a local variable that would not exist in the global context.

Wait, Common Lisp’s `macrolet` doesn’t allow you to depend on a local variable, right? How would you write it in CL?

---

<div class="post-metadata">

**Author:** ![Mark\_Nahabedian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mark_nahabedian/32/16562_2.png) [@Mark\_Nahabedian](https://discourse.julialang.org/u/Mark_Nahabedian)\
**Post date:** [December 15, 2021, 7:40pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/6 "2021-12-15T19:40:22Z")

</div>

What good would it be otherwise?

foo.lisp:

```julia
(defclass reference ()
  ((dict :initarg :dict
         :reader reference-dict)
   (name :initarg :name
         :reader reference-name)))

(defmethod value ((r reference))
  (gethash (reference-name r) (reference-dict r)))

(defvar dict1 (make-hash-table))
(setf (gethash 'foo dict1) 7)
(defvar r1 (make-instance 'reference :dict dict1 :name 'foo))

(assert (eq (value r1) 7))

(let ((x (make-hash-table)))
  (print x)
  (macrolet ((ref (name)
                  `(make-instance 'reference
                                  :dict x
                                  :name ,name)))
     (describe (ref `bar))
     ))

```

```julia
* (load "c:/Users/Mark Nahabedian/foo.lisp")

#<HASH-TABLE :TEST EQL :COUNT 0 {1002B4D393}> 
#<REFERENCE {1002BC4C63}>
  [standard-object]

Slots with :INSTANCE allocation:
  DICT = #<HASH-TABLE :TEST EQL :COUNT 0 {1002B4D393}>
  NAME = BAR
T
* 

```

It’s been a long time. My lisp is really rusty.

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [December 15, 2021, 8:06pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/7 "2021-12-15T20:06:55Z")

</div>

Yeah, but it would work just as well if defined as a global macro, no? Something like

```julia
(macro ref (name)
     `(make-instance 'reference
                     :dict x
                     :name ,name)))

(let ((x (make-hash-table)))
  (print x)
  (describe (ref `bar))))

```

The `x` in your macro expansion is just a symbol, it’ll macroexpand fine in any context (though it’ll be a compile-time error if no `x` is in scope).

You can do the same in julia. Although of course, `MACROLET` is a lot more elegant than a global macro, no argument there.

I do believe there are some cases that can’t be translated into julia. Macros whose expansions contain MACROLET come to mind… I think you basically have to use MacroTools.prewalk/postwalk for those.

---

<div class="post-metadata">

**Author:** ![Mark\_Nahabedian](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mark_nahabedian/32/16562_2.png) [@Mark\_Nahabedian](https://discourse.julialang.org/u/Mark_Nahabedian)\
**Post date:** [December 15, 2021, 8:41pm UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/8 "2021-12-15T20:41:10Z")

</div>

Thanks. I stand corrected.

From the CommonLisp Hyperspec

> The macro-expansion functions defined by macrolet are defined in the lexical environment in which the macrolet form appears. Declarations and macrolet and symbol-macrolet definitions affect the local macro definitions in a macrolet, but the consequences are undefined if the local macro definitions reference any local variable or function bindings that are visible in that lexical environment.

I’ll need to rething this.

---

<div class="post-metadata">

**Author:** ![cstjean](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cstjean/32/1444_2.png) [@cstjean](https://discourse.julialang.org/u/cstjean)\
**Post date:** [December 16, 2021, 11:57am UTC](https://discourse.julialang.org/t/defining-a-macro-in-a-local-scope/73148/9 "2021-12-16T11:57:10Z")

</div>

> [@cstjean](#):
>
> I do believe there are some cases that can’t be translated into julia. Macros whose expansions contain MACROLET come to mind… I think you basically have to use MacroTools.prewalk/postwalk for those.

Come to think of it, with `MacroTools.postwalk`, one could certainly write `MACROLET` and `SYMBOL-MACROLET`. That’d be useful, potentially.
