Stream: helpdesk (published)

Topic: Macro that returns a function


view this post on Zulip mbaz (May 17 2022 at 14:57):

Say I have a package MyPackage that contains a macro @newfunction that returns a function. The macro works perfectly fine, but the functions it returns are defined in the namespace of MyPackage. Is there a way to export the function to the namespace of the caller? The caller might be code running in the REPL or a different package that uses MyPackage.

view this post on Zulip mbaz (May 17 2022 at 15:00):

Of course one could do myfunction = @newfunction args. What I'm wondering is if there is a way to do @newfunction myfunction args, AND have myfunction exported to the namespace of the caller.

view this post on Zulip Mason Protter (May 17 2022 at 15:02):

You just have to esc the name of the function in the macro body

view this post on Zulip Mason Protter (May 17 2022 at 15:03):

e.g.

macro foo(name)
    escname = esc(name)
    :($escname(x) = x + 1)
end

view this post on Zulip mbaz (May 17 2022 at 15:07):

Yeah, this works! Thank you.

view this post on Zulip mbaz (May 17 2022 at 15:11):

I think I understand hygiene a little bit better now.

view this post on Zulip Mason Protter (May 17 2022 at 15:15):

Yeah it’s a kinda tricky concept at first but it’s not so complicated. Julia handles it a little different from most lisps in that it’s hygienic by fault instead of by opting in.

view this post on Zulip Mason Protter (May 17 2022 at 15:16):

Though I often end up just escing the entire returned expression and then manually opting into the hygiene where I choose because I find that easier to think about

view this post on Zulip mbaz (May 17 2022 at 15:21):

This is the first non-trivial macro I write (in Julia or any language). At least for me, this is one instance where the documentation only starts to make sense after I've tried writing something and failing.

In this case, I didn't associate my problem with hygiene. After seeing your solution, I re-read that section in the manual and sure enough, it says that escaped expressions are evaluated in the context of the caller. That sentence didn't make sense to me before, it certainly does now :grinning:

view this post on Zulip Mason Protter (May 17 2022 at 15:21):

so like to adapt the above example, I would often write something more like

macro foo(name)
    @gensym x
    :($name($x) = $one($x) + $x) |> esc
end

here, everything is escaped so all symbols are expanded in the callers namespace, but I manually interpolated my one function into the expression to make sure it gets used rather than the caller’s one (in case they made that symbol mean something different) and I manually mangled the symbol x

view this post on Zulip Mason Protter (May 17 2022 at 15:24):

Either style has pitfalls, but I don’t like the look of $(esc(x))all over my code, so I often gravitate towards the second style.

view this post on Zulip mbaz (May 17 2022 at 15:26):

I think it looks very clean, and certainly more readable than having esc all over the place.

view this post on Zulip Mark Kittisopikul (May 17 2022 at 16:16):

I would also consider using the @generated macro here.

view this post on Zulip Mason Protter (May 17 2022 at 16:17):

Why?

view this post on Zulip Mark Kittisopikul (May 17 2022 at 16:24):

I may be misunderstanding the question, but isn't @generated essentially a macro that returns a function?

view this post on Zulip Mason Protter (May 17 2022 at 16:30):

Sorta. It’s a way to create a custom function where the function body is programmatically generated based on the input types.

It’s a pretty heavy piece of machinery though and not always necessary

view this post on Zulip mbaz (May 17 2022 at 16:32):

I don't think it's necessary in my case, since the input types are not really important.


Last updated: Dec 28 2024 at 04:38 UTC