# Argument confusion when calling macro with semicolon parameter syntax

**URL:** https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716
**Category:** General Usage
**Tags:** macros
**Created:** [September 10, 2023, 9:45am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716 "2023-09-10T09:45:50Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![hhaensel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hhaensel/32/1207_2.png) [@hhaensel](https://discourse.julialang.org/u/hhaensel)
#### Post date: [September 10, 2023, 9:45am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/1 "2023-09-10T09:45:50Z")

</div>

Applying semicolon parameter syntax generates a surprising order of expressions;  
parameters go in the first place, followed by the expected order of arguments

```julia
julia> macro m1(expressions...)
           @show expressions
           return nothing
       end
@m1 (macro with 1 method)

julia> @m1(a, b = "b"; c = "c")
expressions = (:($(Expr(:parameters, :($(Expr(:kw, :c, "c")))))), :a, :(b = "b"))

```

So far so good, one can adapt the macro accordingly. It gets confusing when one tries to require certain parameters

```julia
julia> macro m2(x, expressions...)
           @show x expressions
           return nothing
       end
@m2 (macro with 1 method)

julia> @m2(a, b = "b"; c = "c")
x = :($(Expr(:parameters, :($(Expr(:kw, :c, "c"))))))
expressions = (:a, :(b = "b"))

```

So the order of parameters is the same as before but it is wrongly attributed to the parameter names. Is this use of macros not recommended?  
It is very handy when forwarding keyword arguments to functions.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [September 11, 2023, 6:36am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/2 "2023-09-11T06:36:13Z")

</div>

I usually don’t use keyword arguments with macros using the semicolon syntax. I pass them as regular arguments, and with a little bit of magic in the body of the macro, convert every assignment argument into a keyword argument:

```julia
macro m(args...)
    kwargs = [Expr(:kw, arg.args...) for arg in args
              if Meta.isexpr(arg, :(=))]
    args = filter(arg -> !Meta.isexpr(arg, :(=)), args)
    # and then do whatever you like with kwargs and args
    @show kwargs args
    nothing
end

julia> @m 1 2 x=3 4 y=5
kwargs = Expr[:($(Expr(:kw, :x, 3))), :($(Expr(:kw, :y, 5)))]
args = (1, 2, 4)

```

---

<div class="post-metadata">

### Author: ![hhaensel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hhaensel/32/1207_2.png) [@hhaensel](https://discourse.julialang.org/u/hhaensel)
#### Post date: [September 11, 2023, 7:42am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/3 "2023-09-11T07:42:01Z")

</div>

I didn’t know `Meta.isexpr`, which is very convenient instead of writing `arg isa Expr && arg.head == (:=)`.  
Thanks! 😀

Otherwise my way of argument handling is

```julia
for arg in args
    Meta.isexpr(arg, :(=)) && (arg.head = :kw)
end

```

which is an in-place version of yours.

---

<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: [September 11, 2023, 7:44am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/4 "2023-09-11T07:44:11Z")

</div>

For whatever reason, the semicolon expression puts the keywords first. You can see it in the `Expr` structure:

```julia
julia> dump(:( a, b = "b"; c = "c" ))
Expr
  head: Symbol tuple
  args: Array{Any}((3,))
    1: Expr
      head: Symbol parameters
      args: Array{Any}((1,))
        1: Expr
          head: Symbol kw
          args: Array{Any}((2,))
            1: Symbol c
            2: String "c"
    2: Symbol a
    3: Expr
      head: Symbol =
      args: Array{Any}((2,))
        1: Symbol b
        2: String "b"

```

`@blah(a, b; c=C)` doesn’t work on 1 expression like `@blah (a, b; c=C)` and does separate into 3 inputs, but it _does_ parse the expression `(a, b; c=C)` first, hence the keywords coming first. It’s better to avoid the semicolon in calls because the space-separated syntax `@blah a b c=C` is only equivalent to the call syntax `@blah(a, b, c=C)`.

---

<div class="post-metadata">

### Author: ![HanD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hand/32/213908_2.png) [@HanD](https://discourse.julialang.org/u/HanD)
#### Post date: [September 11, 2023, 8:17am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/5 "2023-09-11T08:17:39Z")

</div>

Ah, I haven’t realized `Expr` was mutable. Nonetheless, I tend to prefer functional style and no in-place modification, when there is no performance penalty, or it is irrelevant.

---

<div class="post-metadata">

### Author: ![hhaensel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hhaensel/32/1207_2.png) [@hhaensel](https://discourse.julialang.org/u/hhaensel)
#### Post date: [September 11, 2023, 9:08am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/6 "2023-09-11T09:08:19Z")

</div>

I understand that in-place modification in general is sometimes risky, but in this case it is just shorter to write and all args can be passed just as is to any function, you don’t even have to split args and kwargs.

Concerning inplace modification just be aware that your

> [@HanD](#):
>
> `args = filter(arg -> !Meta.isexpr(arg, :(=)), args)`

will pass on the args as they are and you may still be modifying your args in-place, e.g.

```julia
macro m(args...)
    newargs = filter(arg -> !Meta.isexpr(arg, :(=)), args)
    for arg in newargs
        if Meta.isexpr(arg, :vect)
            arg.args = vcat(["test"], arg.args)
        end
    end
    :($args)
end

@m [1, 2, 3, 4] ["a", "b", "c"]
# (:(["test", 1, 2, 3, 4]), :(["test", "a", "b", "c"]))

```

---

<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: [September 12, 2023, 11:07pm UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/7 "2023-09-12T23:07:52Z")

</div>

> [@hhaensel](#):
>
> will pass on the args as they are and you may still be modifying your args

`filter`ed elements are `===` equal to elements in the input iterable, not deep-copied, so this is always the case when the elements are mutated. I think HanD just meant “`# and then do whatever you like with kwargs and args`” does not involve mutation e.g. constructing `Expr(:kw, arg.args...)` instead of mutating `arg.head = :kw`. Personally I do mutation if it’s simpler to write _and_ I didn’t need to keep the original `Expr`ession for something else, and if that ends up changing, I don’t have to replace the mutation code because I could just `deepcopy` the `Expr`ession.

---

<div class="post-metadata">

### Author: ![hhaensel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hhaensel/32/1207_2.png) [@hhaensel](https://discourse.julialang.org/u/hhaensel)
#### Post date: [September 13, 2023, 8:04am UTC](https://discourse.julialang.org/t/argument-confusion-when-calling-macro-with-semicolon-parameter-syntax/103716/8 "2023-09-13T08:04:07Z")

</div>

I totally agree. It’s quite the same practice that I have adopted over time …  
So let’s close this, I think we agree that argument handling of macros has some pitfalls but they will probably not be changed due to performance and compatibility reasons.  
Thanks everyone to their comments and inputs!
