Use multi-line arguments to macros without commas with a (new) block structure

question
proposal

#1

Hello, I just thought about the option to use multi-line statements for macro calls without the need to use comma in them.

So I am interested in being able to rewrite this

@mymacro a b "123" (1,2,3)

into this

@mymacro somebegin 
    a b 
    "123" 
    (1,2,3)
end

where somebegin is something which allows to do exactly this (it does not work with begin or quote of course).

I have seen 5622 and this 8980 so I know that in principle this is not possible.

However, I can imagine that the step to allow it through a somebegin statement would be really great and would fit into julia syntax nicely, the only this is basically the newlines which have to be neglected before go to the real julia call.

Could you imagine how I could manage this or even whether it would be a nice feature for the language?

Thanks.


#2

Not sure what you are asking about. With begin ... end, you also get an AST you can work with. I am assuming you want to skip or customize begin?

I came to Julia from Common Lisp, as first I was missing the equivalent of

(somemacro (header arguments)
    followed by
    body)

but now I think that begin is not that much of a hassle and keeps the code readable. This is one of the trade-offs between using S-expressions (nice for macros) and and Algol-like syntax (which can be very compact with various special operators, like in Julia).

In addition to potentially being confusing to someone who did not read the docs/source for a particular macro, constructs like somebegin would require editor indentation macros to parse and understand macro definitions.


#4

Well, thanks for the answer. What I am after is a way to skip braces and commas for long lists of arguments by providing a block structure for this.

When I use a macro with this call @mymacro a b c 1 2 3 I can access each separate argument in the argument list, work with them and then, e. g., call a function by passing this arguments. However, I cannot do the very same thing with a begin ... end syntax because I get an expression and not a list of arguments. O do I misunderstand something?

So I cannot call my macro with

@mymacro begin 
    a
    b 
    c 1 
    2 3
end

and deal with it in the same way I would for the single line…

The story behind this question is actually my personal plotting framework which I use for calling PyPlot.

I would like to simplify lines like this

@plot Plot1 :points ([1,2,3], [4,5,6]) label = "x"
@plot Plot1 :legend
@plot Plot1 :xlim 1 3

to

@plot begin 
    Plot1 
    :points ([1,2,3], [4,5,6]) label = "x"
    :legend
    :xlim 1 3
end

In this particular case it is not much shorter but I think you can see the point.

Of cause I would need to modify the definition of @plot by that is another story…


#5

You can parse both blocks and macro arguments, eg

macro foo(a, b)
    :($a + $b)
end

macro foo(block)
    @assert block.head == :block
    a, b = filter(x -> !(x isa LineNumberNode), block.args)
    :($a + $b)
end

macroexpand(Main, :(@foo 1 2))

macroexpand(Main, :(@foo begin
                    1
                    2
                    end))

That said, I would stick to a single syntax.


#6

Oh, that’s fantastic :wink: Thanks… Have not thought that is can be parse, though. Than the whole question is probably meaningless.


#7

Would something like this be useful for you?

macro mymacro_str(s)
    Meta.parse(string("@mymacro ", replace(s, r"\r?\n" => " ")))
end

macro mymacro(args...)
    # whatever you want to do with the args of @mymacro, for example
    for arg in args
        println(arg)
    end
end
julia> mymacro"""
        a b 
        "123" 
        (1,2,3)
        """
a
b
123
(1, 2, 3)

#8

Regarding the complexity of the input this would end up in very unreadable text because the syntax highlighting will not work in the string context. But in principle this is also possible. Thanks.


#9

I think you’re asking for something like https://github.com/JuliaLang/julia/pull/29273. I don’t think that particular PR will ever be merged (too much unicode), but I still hope for something equivalent. In the meantime I use the hack of “commenting out the newline”:

@mymacro      #= 
=#  a b       #=
=#  "123"     #=
=#  (1,2,3)

Of course, if you have control over @mymacro you can support parsing of a block as well. However this is a fairly unusual way to pass multiple arguments to a macro and I’d say a bit unnatural in the context of the larger Julia ecosystem.


#10

That is a nice trick :wink: Also thanks for the link.

I agree that it is somehow not julia-ish style to leave out all the commas in the call. However, as it is a rich-macro using language I thought that a way of changing the style according to your needs is also a valid way :wink:


#11

That’s not really what I mean; it’s actually much more common to leave out the commas in macro calls (at least in my experience). Personally I prefer the space separated style in macro calls which is why I wrote the linked PR.

All I mean is that having a macro which treats

@mymacro begin
a
b
end

the same as @mymacro a b is a fairly unusual style. But it’s quite workable. If it does what you need don’t let me discourage you :wink:


#12

Yes, I think that maybe in my case something like

@plot Plot :points [1, 2, 3] [4, 5, 6]

and

@plot Plot begin
    :points [1, 2, 3] [4, 5, 6] 
end 

would be the cleanest way. Thanks for the discussion.


#13

The downside there is that the parsing rules are different in a block vs a macro argument list so your second version above is invalid syntax. You’d need to write it as

@plot Plot begin
    :points
    [1,2,3]
    [4,5,6]
end

Alternatively (ab)use some other space sensitive context like matrix construction:

@plot Plot [
    :points [1,2,3] [4,5,6]
]

#14

That’s a great suggestion. See PGFPlotsX.@pgf, which uses { ... } which is not used for anything else.