# Heavy macro use or not?

**URL:** <https://discourse.julialang.org/t/heavy-macro-use-or-not/115097>\
**Category:** Community\
**Created:** [June 3, 2024, 6:07am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097 "2024-06-03T06:07:30Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tarny\_GG\_Channie](https://avatars.discourse-cdn.com/v4/letter/t/3bc359/32.png) [@Tarny\_GG\_Channie](https://discourse.julialang.org/u/Tarny_GG_Channie)\
**Post date:** [June 3, 2024, 6:07am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/1 "2024-06-03T06:07:30Z")

</div>

Julia macros are powerful, capable of creating its own DSLs and so on. However, the generated code can be really hard to predict. Some could argue that the bird eye’s view of the code functionality is more important than the precise control flow used to achieve that functionality, thus, one should use macros to create a DSL that expresses the necessary details about the needed functionality whenever it is more compact than expressing it as functions. Some could argue adding functionality as a macro makes it hard to compose the functionality with others. Let’s make a poll to see which camp you’re on. Funnily enough, SciML has a guideline against that, but does that anyway in ModelingToolkit.

_Poll ([view on site](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/1))_

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [June 3, 2024, 6:14am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/2 "2024-06-03T06:14:17Z")

</div>

I think that the wording of this poll will bias the results towards the second option. Even most people who like the use of macros wouldn’t encourage a “heavy” or “excessive” use.

---

<div class="post-metadata">

**Author:** ![Tarny\_GG\_Channie](https://avatars.discourse-cdn.com/v4/letter/t/3bc359/32.png) [@Tarny\_GG\_Channie](https://discourse.julialang.org/u/Tarny_GG_Channie)\
**Post date:** [June 3, 2024, 6:21am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/3 "2024-06-03T06:21:30Z")

</div>

Oops… my bad. Let’s change the poll.

_Poll ([view on site](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/3))_

---

<div class="post-metadata">

**Author:** ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)\
**Post date:** [June 3, 2024, 6:54am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/4 "2024-06-03T06:54:30Z")

</div>

Our codebase has 34,000 lines of Julia, and 6 macros.

One of those macros is pretty critical and enables our in-house DSL, shaving thousands of lines off our codebase. It’s 60 lines long, pretty complex, and has been the source of a lot of bugs – but still very very high value. Our DSL is a little weird since it looks like normal Julia code, but lazily evaluated by building a DAG.

The remaining five I would describe as conveniences. I would miss them, but they’re nowhere close to essential. They’re also all much smaller, ~5-10 lines each.

I’m not sure where that falls in your poll, but I would guess “heavy use”.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 3, 2024, 8:49am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/5 "2024-06-03T08:49:28Z")

</div>

Some related discussion here: [How to discourage macros: disable automerge of General registry PRs?](https://discourse.julialang.org/t/how-to-discourage-macros-disable-automerge-of-general-registry-prs/104529)

I still feel almost all macro definitions I see in the ecosystem are unnecessary and regrettable.

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [June 3, 2024, 9:51am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/6 "2024-06-03T09:51:01Z")

</div>

Macros have caused some of the strangest errors I’ve encountered in any programming language. For instance, in Julia, writing `f (x)` results in an error because of the space between the function name and the parentheses.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [June 3, 2024, 10:23am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/7 "2024-06-03T10:23:17Z")

</div>

Sorry, I do not understand what this have to do with macros. This is the syntax of the language,with or without macros.

---

<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:** [June 3, 2024, 10:41am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/8 "2024-06-03T10:41:01Z")

</div>

Macros transform code, that’s their use case. The issue is understanding when you need to transform code, and transforming code needlessly is obviously terrible. The rule of thumb from an old talk is consider writing functions, higher order functions, then macros; it turns out base Julia didn’t need to define many macros, and many of them just process more convenient function call code before calling an also useful function that does the real work. Calling macros however is routine (how else are you going to `@inline`?)

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [June 3, 2024, 10:58am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/9 "2024-06-03T10:58:27Z")

</div>

> [@Henrique\_Becker](#):
>
> Sorry, I do not understand what this have to do with macros.

The original rationale for disallowing `f (x)` was related to macro semantics but it’s also problematic in constructions like `[f (x)]`. See [make `f (a,b)` an error · Issue #7232 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/7232) for the full story.

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [June 3, 2024, 11:40am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/10 "2024-06-03T11:40:11Z")

</div>

That’s true, but the array is special syntax anyway, so `f (x)` is not to blame here. Similar to `[2 +5im]`, any one would expect 2 elements.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [June 4, 2024, 7:25am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/11 "2024-06-04T07:25:27Z")

</div>

> [@nsajko](#):
>
> I still feel almost all macro definitions I see in the ecosystem are unnecessary and regrettable.

Macros can be fantastic (if not strictly necessary) for eliminating boilerplate, e.g. in `BaseDirs.jl` I have

```julia
@setxdg DATA_HOME "~/.local/share"
@setxdgs DATA_DIRS ["/usr/local/share", "/usr/share"]
@setxdg CONFIG_HOME "~/.config"
@setxdgs CONFIG_DIRS ["/etc/xdg"]
@setxdg STATE_HOME "~/.local/state"
@setxdg BIN_HOME "~/.local/bin"
@setxdg CACHE_HOME "~/.cache"

```

Each invocation of `@setxdg` is saving 3 lines (4 → 1) and improving code clarity. There are 49 uses of this macro across the codebase, IMO this is well worth it.

* * *

In `DataToolkit.jl`, I define a few macros such as `@addpkg`, `@require`, and `@advise`.

These _could_ all be removed in favor of functions, but once again clarity and readability would suffer. Manually passing in `@ __MODULE__ ` every time you invoke a function (when it’s a commonly called function) is also just a bit of a pain.

* * *

String macros are also rather handy, as an example I’d pick the IMO indispensable ~500 line (yes, it’s that big) string macro in `StyledStrings.jl`.

```julia-auto
styled"{bold,(bg=yellow):hey} {green:there $you}"

# The above expands to (cleaned up):

annotatedstring(
    AnnotatedString("hey there ",
                    [(1:3, :face => :bold),
                     (1:3, :face => Face(background=:yellow)),
                     (5:10, :face => :green)]),
    let you_str = string(you)
        you_len = ncodeunits(you_str)
        if you_str isa AnnotatedString && !(isempty(you_str))
            AnnotatedString(String(you_str),
                            vcat([1:you_len, :face => :green],
                                 annotations(you_str)))
        else
            if isempty(you_str)
                ""
            else
                AnnotatedString(you_str, [(1:you_len, :face => :green)])
            end
        end
    end) |> annotatedstring_optimize!

```

* * *

Maybe I’m missing something, but I would find it hard to classify the above as “unnecessary and regrettable”.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [June 4, 2024, 11:11am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/12 "2024-06-04T11:11:07Z")

</div>

Macros can be awesome for providing convenient access to complex APIs.

But I utterly hate cases where macros are the primary documented API. Even worse are cases where macros are the only documented public stable API!

Look at e.g. BenchmarkTools for how not to do it. It is nigh impossible and mostly undocumented how to use that without macros.

In a saner world, they would document a function `makeBenchmarkable(expr::Expression)` that e.g. creates some benchmarkable object from an expression, and then would describe that e.g. `@btime` et al are thin wrappers over that function (take the expression, `makeBenchmarkable`, then run the benchmark).

This is also the case in the C world. It is utterly disgusting to have a C library with publicly documented macro-only APIs. Come on, please describe the exported symbols of your shared library, not your C-centric syntactic sugar.

> [@Benny](#):
>
> Calling macros however is routine (how else are you going to `@inline`?)

That’s not a “real” complex macro.

It’s just a decision to not use language keywords for stuff like `@goto / @label / @inline` and instead overload the macro machinery. Same for the new atomics stuff.

I can live with that, but this is not the kind of macro use that is contentious.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 4, 2024, 11:43am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/13 "2024-06-04T11:43:50Z")

</div>

> [@foobar\_lv2](#):
>
> In a saner world, they would document a function `makeBenchmarkable(expr::Expression)` that e.g. creates some benchmarkable object from an expression

Even better, `makeBenchmarkable` could take a callable object.

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [June 4, 2024, 11:45am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/14 "2024-06-04T11:45:28Z")

</div>

> [@tecosaur](#):
>
> Macros can be fantastic (if not strictly necessary) for eliminating boilerplate, e.g. in `BaseDirs.jl` I have
> 
> ```julia
> @setxdg DATA_HOME "~/.local/share"
> @setxdgs DATA_DIRS ["/usr/local/share", "/usr/share"]
> @setxdg CONFIG_HOME "~/.config"
> @setxdgs CONFIG_DIRS ["/etc/xdg"]
> @setxdg STATE_HOME "~/.local/state"
> @setxdg BIN_HOME "~/.local/bin"
> @setxdg CACHE_HOME "~/.cache"
> 
> ```
> 
> Each invocation of `@setxdg` is saving 3 lines (4 → 1) and improving code clarity.

It’s difficult to say from just the invocation snippet, but this seems like it should have been a single data structure construction call, perhaps just a named tuple, instead of seven different (macro) calls?

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [June 4, 2024, 11:52am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/15 "2024-06-04T11:52:07Z")

</div>

> [@nsajko](#):
>
> It’s difficult to say from just the invocation snippet, but this seems like it should have been a single data structure construction call, perhaps just a named tuple, instead of seven different (macro) calls?

For reference, `@setxdg CONFIG_HOME "~/.config"` expands to

```julia
XDG_CONFIG_HOME[] = if haskey(ENV, "XDG_CONFIG_HOME") && !isempty(ENV["XDG_CONFIG_HOME"])
    chopsuffix(ENV["XDG_CONFIG_HOME"], Base.Filesystem.path_separator)
else
    expanduser("~/.config")
end

```

it’s not complicated, but you can’t get around the boilerplate from having `XDG_CONFIG_HOME[]` + `"XDG_CONFIG_HOME"` without a macro. Is this worth exposing to a user? No, but it’s nice for internal usage.

The rest of my examples are more public-facing.

---

<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:** [June 4, 2024, 11:58am UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/16 "2024-06-04T11:58:44Z")

</div>

> [@foobar\_lv2](#):
>
> e.g. creates some benchmarkable object from an expression, and then would describe that e.g. `@btime` et al are thin wrappers over that function (take the expression, `makeBenchmarkable`, then run the benchmark)

You mean like [`run(b::Benchmark[, p::Parameters = b.params]; kwargs...)`](https://juliaci.github.io/BenchmarkTools.jl/stable/reference/#Base.run)?

That’s certainly not without macros, the `Benchmark` instance is made in the expression returned by `@benchmarkable`. But that’s the thing, this isn’t a generic instance-in instance-out situation, you are actually transforming code and `eval`ing functions in the global scope besides making that `Benchmark` instance, which is justifiably a macro’s jurisdiction.

Macros are really functions deep down, so if you really want to you can get at the part between the `Meta.parse` and `eval` steps; the cleanest stable way I know is `macroexpand(inputModule, :(@inputmacro $(inputExprs...)))`. This and an `eval` could be wrapped in a function that applies a macro to a runtime `Expr` at a global scope, but not many will prefer an inconvenient cosmetic change `@foo blah()` → `foo(@ __MODULE__ , :(blah()))` to transform source code. Note that such a function isn’t a drop-in replacement for a macro call in general because of how multiple macro calls in local scopes are expanded before a global `eval`. Not everything does its work in the global scope like `BenchmarkTools`.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [June 4, 2024, 1:07pm UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/17 "2024-06-04T13:07:06Z")

</div>

> [@tecosaur](#):
>
> For reference, `@setxdg CONFIG_HOME "~/.config"` expands to

> [@tecosaur](#):
>
> you can’t get around the boilerplate from having `XDG_CONFIG_HOME[]` + `"XDG_CONFIG_HOME"` without a macro

There is no reason to make the API for setting XDG config variables a macro. You’re only using the a macro in order to relate the name of the variable to the default, via string ops.

The package that originally defines the string variables already knows these relations. So it could have

```julia
module XdgStuff
const XDG_defaults = Dict{Base.RefValue{String}, String}()
const XDG_CONFIG_HOME = Ref("")
XDG_defaults[XDG_CONFIG_HOME] = "XDG_CONFIG_HOME"
...

function setxdg(r::Base.RefValue{String}, v::String)
key = XDG_defaults[r] #maybe throw with a better error message if users try to set unregistered xdg configs?
r[] = (haskey(ENV, key) && !isempty(ENV[key])) ? chopsuffix(ENV[key], Base.Filesystem.path_separator) : v
r[]
end

```

allowing for

```julia
setxdg(XDG_CONFIG_HOME, "~/.config")

```

The string stuff can also be done via macros, but these would not be user-facing API, they would be used to define the exported XDG config variables and associate them to the environment variables.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [June 4, 2024, 1:09pm UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/18 "2024-06-04T13:09:00Z")

</div>

> [@foobar\_lv2](#):
>
> but these would not be user-facing API, they would be used to define the exported XDG config variables and associate them to the environment variables.

That is exactly what my example is. Reducing boilerplate in package internals.

See my other examples for public-facing macros.

---

<div class="post-metadata">

**Author:** ![non-Jedi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/non-jedi/32/3645_2.png) [@non-Jedi](https://discourse.julialang.org/u/non-Jedi)\
**Post date:** [June 4, 2024, 3:37pm UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/19 "2024-06-04T15:37:49Z")

</div>

> [@tecosaur](#):
>
> For reference, `@setxdg CONFIG_HOME "~/.config"` expands to
> 
> ```julia
> XDG_CONFIG_HOME[] = if haskey(ENV, "XDG_CONFIG_HOME") && !isempty(ENV["XDG_CONFIG_HOME"])
> chopsuffix(ENV["XDG_CONFIG_HOME"], Base.Filesystem.path_separator)
> else
> expanduser("~/.config")
> end
> 
> ```
> 
> it’s not complicated, but you can’t get around the boilerplate from having `XDG_CONFIG_HOME[]` + `"XDG_CONFIG_HOME"` without a macro. Is this worth exposing to a user? No, but it’s nice for internal usage.

What advantage does this give you as a macro instead of as a function dispatching on a `CONFIG_HOME` singleton? I assume the macro generates different code based on what OS you’re running on, but that could also be handled as a branch in a function, right?

```julia
struct ConfigHome end
const CONFIG_HOME = ConfigHome()
function setxdg(::ConfigHome, value)
    XDG_CONFIG_HOME[] = if haskey(ENV, "XDG_CONFIG_HOME") && !isempty(ENV["XDG_CONFIG_HOME"])
        chopsuffix(ENV["XDG_CONFIG_HOME"], Base.Filesystem.path_separator)
    else
        expanduser(value)
    end
end

```

Honestly I don’t mind macros except when they cause problems by not composing, so I couldn’t care less about internal macros; I’m just curious why you structured it this way. I feel like I’d have definitely reached for a function.

---

<div class="post-metadata">

**Author:** ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)\
**Post date:** [June 4, 2024, 3:43pm UTC](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097/20 "2024-06-04T15:43:20Z")

</div>

> [@non-Jedi](#):
>
> What advantage does this give you as a macro instead of as a function dispatching on a `CONFIG_HOME` singleton?

Laziness/conciseness: Given I’ve got about a dozen invocations of `@setxdg` per OS, writing a singleton for each variable assignment would be a fair bit of boilerplate.

It’s just a simple little macro that works and makes the internals a little nicer 🙂

[Next page](https://discourse.julialang.org/t/heavy-macro-use-or-not/115097.md?page=2)
