Static @capture instead of that of MacroTools.jl: more efficient, more functionalities, more straightforward

macros

#1

To leverage MLStyle.jl’s high performance, we’ve demonstrated an implementation of @capture with few codes, to replace that(@capture) of MacroTools.jl.

@info @capture f($x) :(f(1))
# Dict(:x=>1)

destruct_fn = @capture function $(fname :: Symbol)(a, $(args...)) $(body...) end

@info destruct_fn(:(
    function f(a, x, y, z)
        x + y + z
    end
))

# Dict{Symbol,Any}(
#     :args => Any[:x, :y, :z],
#     :body=> Any[:(#= StaticallyCapturing.jl:93 =#), :(x + y + z)],
#    :fname=>:f
# )

We don’t have a plan to make a new MacroTools.jl, but we do hope that the author of MacroTools.jl can pay necessary attention to MLStyle.jl’s expr patterns and AST patterns.

Benchmark result: https://github.com/thautwarm/MLStyle.jl#benchmark


#2

Could you summarise briefly what this is for? Do you have applications in mind for which the speed of @capture from MacroTools is a bottleneck?


#3

Could you summarise briefly what this is for?

A brief summary is, we hope MacroTools.jl could use MLStyle.jl to achieve such a @capture.

AYC, currently, MacroTools.jl’s @capture uses underscore to denote the variables to be capture, e.g., struct typename_ fields__ end, which is quite an inconsistent notation that makes many people including me feel uncomfortable. Although there is a background about why don’t use $,


it’s not that correct for capturing $ expression is also very rare, so you can transform Expr(:&, arg) into something else and then perform capturing if necessary.
The main advantages of $ notation instead of underscore are

  1. Straightforward. In the issue referred above, people think so. Julia might be the only one who’s homoiconic except Lisp idioms, which is the root reason why Julia macros work. We should follow how ASTs are inserted( :($a + b) ), and use this notation as how ASTs are “uninserted”/captured.

  2. You can introduce other pattern matching into @capture, like type matching struct $(typename :: Symbol) ..., packing [1, 2, $(a...), last] and so on.

Do you have applications in mind for which the speed of @capture from MacroTools is a bottleneck?

Usually, we don’t care about performance of the tasks that MacroTools.jl is for. Macros are often used for codegen or staging computations, and even if your macro processing is very slow, only the first time you load a module would be affected.
However, still some scenario can be performance sensitive. Some people who prefer DSLs(domain specific languages) might use MacroTools to rewrite almost all part of the ASTs, which causes that a tiny modification on a DSL would produce totally new ASTs. In this case, MacroTools would be regarded as something like a parser, which is performance significant.


#4

@MikeInnes


#5

OK, thanks for the explanation!

I had no idea re-useing interpolation syntax for pattern matching was a common desire — I assumed the underscores came from Mathematica (including i_Int etc.) and quite like the lack of clutter.

Did you find a way around the issues with parsing differences (this comment)?

My guess is that changing MacroTools is very unlikely, as this would break every single package using it. It might be a good idea to use a different names, to make it easier for anyone to add this package & change uses one by one (or only change new code).

Edit: wait is @capture defined, or is this a suggestion to rename something which is defined? First playground example:

julia> using MLStyle
julia> @info @capture f($x) :(f(1))
ERROR: LoadError: UndefVarError: @capture not defined

#6

Hey, friend, I agree with you at all.
The workaround of parsing issues could be solved with rewriting all Expr(:$, args...) into something without $ like Expr(a_symbol, args...) where a_symbol is a Symbol not used as keywords like let, function and so on.
In terms of your codes that throws UnderVarError, I’d say sorry to you for you might get confused about my README.

@capture is not defined in the MLStyle.jl, but there are several implementations as tutorials of MLStyle.jl.


#7

Oh, I didn’t refer to correct comment.
If you want to capture that, in MLStyle.jl, you should this notation: struct $T; $(_...) end.
You should remember MLStyle.jl provides the way to destructure ASTs just as how they’re written by humans.


#8

Neat stuff! I assume you are compiling the template to get specialised matching code. The main reason MacroTools didn’t do this originally is that (a) performance inside a macro is not that big of a deal and (b) it’s occasionally useful to match against runtime expressions, rather than literal ones. (a) could certainly change as we use Expr for more things. (b) isn’t necessarily a huge deal breaker but it’s useful to be aware of.


#9

Understood.
In fact I haven’t taken (b) into consideration. If we want to use a runtime expression as the template, it might be some cases that the template wouldn’t be used many times. It’s worth to compile the template only if the matching code is hot. You’re right.

However I’d still suggest to update the syntax of @capture, this is the motivation of some contributor of MLStyle.jl.
If people can do capturing just as the inverse operation of AST interpolation, things could be then much easier.