# Macro hygiene with try/catch

**URL:** https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468
**Category:** General Usage
**Tags:** question, macros
**Created:** [March 24, 2020, 10:07pm UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468 "2020-03-24T22:07:47Z")
**Posts on this page:** 15
**Page:** 1

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 24, 2020, 10:07pm UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/1 "2020-03-24T22:07:47Z")

</div>

I have a macro that is supposed to define a new function in the caller’s scope (plus do several other things). The code to be evaluated is wrapped in a `try/catch` block so that if something fails, I can clean things up. However, the functions get renamed by the macro expander (hygiene), even though the whole expression is wrapped in `esc`. MWE:

```julia
macro test()
	fun_def = :( foo(x) = 2*x )

	# ( ... modify some global state ... )
	
	return esc(
		quote
			try
				# (... do some stuff that might fail ...)
				$fun_def
			catch e
				# ( ... revert the modification of the global state ... )
				rethrow(e)
			end
		end
	)
end

julia> @test
(::getfield(Main, Symbol("#foo#13"))) (generic function with 1 method)

```

It has been [suggested](https://github.com/JuliaLang/julia/issues/27554) that this may have something to do with the local scope introduced by `try`. I have tried various ways to make `fun_def` escape hygiene, but none of them work. Any suggestions?

---

<div class="post-metadata">

### Author: ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)
#### Post date: [March 24, 2020, 10:47pm UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/2 "2020-03-24T22:47:34Z")

</div>

`try` (just like every other expression) has a returned value, in this case the last expression in the `try` block itself. You can create a variable in the scope where the macro is expanded by assigning the result of the `try` to a variable:

```julia
function outer()
  fun_def = try
    function(a, b, c)
      a + b + c
    end
  catch e
    println("nope!")
  end

  @show fun_def(1, 2, 3)
end

```

Your macro just needs to generate the `fun_def = try ... end` code.

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 25, 2020, 2:49am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/3 "2020-03-25T02:49:17Z")

</div>

Thanks for the suggestion, but it seems no better:

```julia
macro test2()
	esc( quote
		func = try
			foo(x) = 2*x
		catch e
			println("nope")
		end
	end )
end

julia> @test2
(::getfield(Main, Symbol("#foo#14"))) (generic function with 1 method)

julia> func
(::getfield(Main, Symbol("#foo#14"))) (generic function with 1 method)

```

I want `foo` to exist in the caller space, not a variable that is bound to the function with a garbled name.

Besides `esc` not seeming to propagate into the `try` block, I also find it odd that `@macroexpand` does not show (in either example) that `foo` gets renamed.

---

<div class="post-metadata">

### Author: ![mauro3](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mauro3/32/292_2.png) [@mauro3](https://discourse.julialang.org/u/mauro3)
#### Post date: [March 25, 2020, 7:33am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/4 "2020-03-25T07:33:17Z")

</div>

@redits suggested to use an anonymous function. But even without that, you’ll find that `func` is defined in your example.

```julia
julia> macro test2(fn)
               esc( quote
                       const $fn = try
                              x -> 2*x
                       catch e
                               println("nope")
                       end
               end )
       end
@test2 (macro with 2 methods)

julia> @test2(foo)
#19 (generic function with 1 method)

julia> foo
#19 (generic function with 1 method)

```

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 25, 2020, 2:55pm UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/5 "2020-03-25T14:55:07Z")

</div>

Ah, I misunderstood the intent. That seems to be an effective workaround. Thanks for the help.

However, if I am honest, I find this workaround unsatisfying. It feels like a hacky way to achieve the clear and obvious intent of the generated code in my first example. (It actually becomes clunkier in my actual use case, in which the macro creates several functions and a new type.) Basically I would like things to work the “normal” way, i.e. like it does when the function is not put in a `try` block: A function named `foo` is added to the global function table, accessible by that name (and not some generated name) to the caller and everyone else who wants to use it. The kind of solution I was hoping for was (1) some syntax for getting `foo` to be escaped, and (2) some explanation of why `esc` does not escape code inside a `try` block.

---

<div class="post-metadata">

### Author: ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)
#### Post date: [March 25, 2020, 11:01pm UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/6 "2020-03-25T23:01:43Z")

</div>

You can use `global` inside `try` to define a global function

```julia
julia> try
           foo() = 1
       catch
       end
(::var"#foo#94") (generic function with 1 method)

julia> foo
ERROR: UndefVarError: foo not defined

julia> try
           global foo() = 1
       catch
       end
foo (generic function with 1 method)

julia> foo
foo (generic function with 1 method)

```

By the way, the issue here is unrelated to macro hygiene (which does not happen if you wrap everything with `esc`). It’s just closure name mangling.

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 26, 2020, 12:49am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/7 "2020-03-26T00:49:04Z")

</div>

Ah, I think that explains everything. I did not realize that functions defined in local scopes (which is evidently what you mean by a closure? I usually think of closures as anonymous functions) get renamed.

---

<div class="post-metadata">

### Author: ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)
#### Post date: [March 26, 2020, 1:41am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/8 "2020-03-26T01:41:50Z")

</div>

Right, closure is probably not the right term here since `foo` doesn’t capture any local variables. Maybe anonymous function was better but I thought using it for `f(x) = ...` form (not `x -> ...` form) could be confusing. Anyway, IIUC they are implemented with the same mechanism.

> [@benninkrs](#):
>
> functions defined in local scopes (which is evidently what you mean by a closure? I usually think of closures as anonymous functions) get renamed.

They don’t get renamed. Anonymous functions and closures don’t get a name [1] in the first place. What you are seeing are the _type_ of them.

This is because, in Julia, you get “global” type for any kind of callable object (though I believe this is an implementation detail). You can see it by doing something like:

```julia
julia> module Demo
       a() = f() = 1
       b() = f() = 1
       c() = () -> 1
       end
Main.Demo

julia> names(Demo, all=true)
14-element Array{Symbol,1}:
 Symbol("#3#4")
 Symbol("#a")
 Symbol("#b")
 Symbol("#c")
 Symbol("#eval")
 Symbol("#f#1")
 Symbol("#f#2")
 Symbol("#include")
 :Demo
 :a
 :b
 :c
 :eval
 :include

julia> Demo.a()
(::Main.Demo.var"#f#1") (generic function with 1 method)

julia> typeof(Demo.a())
Main.Demo.var"#f#1"

julia> typeof(Demo.a()) == getfield(Demo, Symbol("#f#1"))
true

julia> Demo.b()
(::Main.Demo.var"#f#2") (generic function with 1 method)

julia> typeof(Demo.a()) == typeof(Demo.b())
false

julia> Demo.c()
#3 (generic function with 1 method)

julia> typeof(Demo.c())
Main.Demo.var"#3#4"

```

[1]: I think a more precise way to say this is that they don’t get the `show` method reflecting the variable names they are assigned to.

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 26, 2020, 2:38am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/9 "2020-03-26T02:38:44Z")

</div>

Ok, let me make sure I understand your demo:  
`a`, `b`, and `c` are the names of global functions.  
`#a`, `#b`, and `#c` are names of their respective types.  
Each occurrence of `f() = 1` is an anonymous function. Their types are named `#f#1` and `#f#2`.  
`() -> 1` is also an anonymous function, of type `#3#4`.

> They don’t get renamed. Anonymous functions and closures don’t get a name [1] in the first place.

My function _was_ renamed in the sense that I explicitly gave it a name `foo`, but the parser decided to identify it some other way, so that I could not subsequently access it as `foo`.

In retrospect, this behavior logically arises from the fact that `try` introduces a local scope (which I did not realize), and functions defined in local scopes are anonymous (which I also did not realize). The first of these facts is documented in the manual, but may be unexpected to people coming from other languages. And I am not aware that the latter fact is addressed in the manual. It seems that defining things in a `try` block is a fairly natural thing to do, and it might be helpful if the manual had a comment and/or example about this. I would be happy to add this if people think it would be helpful. But if the consensus is more “No, you were doing something kind of weird and you needed to read the manual more carefully” then I will happily move on with greater knowledge than I had a day ago.

---

<div class="post-metadata">

### Author: ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)
#### Post date: [March 26, 2020, 2:45am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/10 "2020-03-26T02:45:21Z")

</div>

Yeah, I think you get it.

> [@benninkrs](#):
>
> > They don’t get renamed. Anonymous functions and closures don’t get a name [1] in the first place.
> 
> My function _was_ renamed in the sense that I explicitly gave it a name `foo`

What do you mean by “name”? This is to say, what is the “name of `g`” in:

```julia
julia> f() = 1
f (generic function with 1 method)

julia> g = f
f (generic function with 1 method)

```

I assumed that what you meant by “name” is whatever printed in Julia REPL. That’s why I said:

> [@tkf](#):
>
> [1]: I think a more precise way to say this is that they don’t get the `show` method reflecting the variable names they are assigned to.

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 26, 2020, 3:02am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/11 "2020-03-26T03:02:44Z")

</div>

In your example `g` doesn’t have a name. But my example was of the form

```julia
try
  foo(x) = 2*x
catch;
end

```

I gave the function the name `foo`. However, what is returned from this is `(::getfield(Main, Symbol("#foo#13")))`.

---

<div class="post-metadata">

### Author: ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)
#### Post date: [March 26, 2020, 3:08am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/12 "2020-03-26T03:08:32Z")

</div>

I think you are mixing the way object is printed and name of the variable. I think the correct statement is “`foo` is not printed as `foo`”, not “`foo` does not have a name” or “`foo` is renamed.”

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 26, 2020, 4:02am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/13 "2020-03-26T04:02:03Z")

</div>

No, it is not simply a matter of how things are displayed:

```julia
julia> try
	foo(x) = 2*x
catch;
end
(::getfield(Main, Symbol("#foo#13"))) (generic function with 1 method)

julia> foo
ERROR: UndefVarError: foo not defined

```

The function I defined is not accessible outside the `try` block by the provided name `foo`. Now, your comments indicate you understand `foo` as a variable, but I think it is not a variable. It is a name (of a function). This is just like when we define `abstract type Bar end`, `Bar` is not a variable, but a name (of a type). That is the semantic and also a [common way of talking about functions](https://docs.julialang.org/en/v1/manual/functions/#), regardless of how they are implemented internally within Julia.

Terminology quibbles aside, I now understand why my original code didn’t do what I expected and how to get it to work, for which I am appreciative.

---

<div class="post-metadata">

### Author: ![tkf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tkf/32/17635_2.png) [@tkf](https://discourse.julialang.org/u/tkf)
#### Post date: [March 26, 2020, 4:16am UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/14 "2020-03-26T04:16:47Z")

</div>

That’s nothing to do with the “name.” As you’ve already noticed, `try` introduces a scope:

```julia
julia> x
ERROR: UndefVarError: x not defined

julia> try
           x = 1
       catch
       end
1

julia> x
ERROR: UndefVarError: x not defined

```

So, any kind of “assignment”, including the function definition, must be explicitly marked as `global` if you want to access it from the global scope.

I’m not sure what is the origin your misunderstanding. But maybe it helps to notice that `foo(...) = ...` is not a syntax for “global” function:

```julia
julia> function f(x)
           g() = x
           g
       end
f (generic function with 1 method)

julia> g
ERROR: UndefVarError: g not defined

julia> f(1)
(::var"#g#1"{Int64}) (generic function with 1 method)

julia> f(1)()
1

```

---

<div class="post-metadata">

### Author: ![benninkrs](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/benninkrs/32/3191_2.png) [@benninkrs](https://discourse.julialang.org/u/benninkrs)
#### Post date: [March 26, 2020, 6:40pm UTC](https://discourse.julialang.org/t/macro-hygiene-with-try-catch/36468/15 "2020-03-26T18:40:06Z")

</div>

I don’t think I’m misunderstanding anything. I agree with everything you said in the preceding post, except your comment that “it has nothing to do with name”. By [definition](https://docs.julialang.org/en/v1/manual/variables-and-scoping/#scope-of-variables-1), a scope is a region of code in which a variable (i.e. name) is visible. Inside the local scope where the function is defined, it is accessible by the name `foo`. That name is not valid outside that local scope, as indicated later in that section of the manual: “local scopes are not namespaces, thus variables in an inner scope cannot be retrieved from the parent scope through some sort of qualified access.”

We evidently have a different understanding of some things and seem unable to convince each other of the correctness of our respective points of view. So I don’t think it is productive to continue this particular discussion. I thank you for providing a solution to my original post, and will now bow out of this conversation.
