Description
Proposal: I'd like to incorporate some form of pattern matching for expressions, in particular ExpressionMatch.jl. I'd love for the macro-writers among you to give feedback and help me hash out the API and implementation.
Why @match
Working with Julia Expr
objects is a pain, requiring an encyclopedic knowledge of implementation details to do the right thing and handle strange edge cases. Pattern matching provides an answer: expressions are described as they are written, so you don't have to remember two representations of everything.
@tkelman points out:
Code that uses pattern matching looks neat, but to me it comes across as a bit of a too-clever novelty syntax pun, and people who've never seen or used pattern matching before (which I'll postulate is the vast majority of Julia users) are going to have a really hard time understanding what's going on.
I think this is a very fair point, and if we were talking about implementing pattern matching over general Julia types I might agree. But working with expressions already has a learning curve above and beyond general types; in order to read or write a macro one must know or work out how every type of expression is internally specified. And that's before you get to the confusing details like the difference between Expr(:quote, :x)
and QuoteNode(:x)
(can you remember which is created by :(Foo.bar)
and @m(Foo.bar)
, I wonder?), which expressions create extraneous begin
blocks for a single element, etc. etc. I've written a ton of macros and these things still trip me up every time – people who haven't spent years banging their heads against Expr
objects (which I'll postulate is the vast majority of Julia users) are going to have a really hard time understanding what's going on.
Consider the readme example:
julia> ex = quote
type Foo
x::Int
y
end
end
julia> if isexpr(ex.args[2], :type)
(ex.args[2].args[2], ex.args[2].args[3].args)
end
(:Foo,{:( # line 3:),:(x::Int),:( # line 4:),:y})
julia> @match ex begin
type T_
fields__
end -> (T, fields)
end
(:Foo,{:(x::Int),:y})
@match
is probably a bit surprising the first time you see it, but that's a one-off cost – it only has to click once and you understand it every time it's used. The syntax x.args[2].args[3].args
may not surprise you initially, but what about its intent? You have to wade through it every time you see it.
Hell, you've only seen it once, and I'll bet you find it much easier to see what's going on here than here. The former adds new features and handles a bunch of edge cases better – can you tell me which ones? (30 seconds, go!) Of course, it's possible to read it in the same way it's possible to read assembly code, but that doesn't mean we have to work with it.
This is a tradeoff, like any other design decision, but it's clear to me at least which side I'd rather be on. If you had to remember how a sparse matrix was implemented every time you used it, we'd never settle for it.
The Case for Base
The doc system snippet above is the shining example of a use case of this, because we want to dispatch on a bunch of different syntax. Checking for things like :(:(Base.@time))
(yes, double-quoted – how is that represented internally again?) and getting the edge cases right is a nightmare. With @match
, it's crystal clear what is and isn't supported by @doc
, and you don't have to be a macro wizard to suggest or implement new ideas – the accessibility of the code is greatly improved, and it's far from a novelty trick. If people can get behind this then I imagine it wouldn't hurt to have "real" support as part of the standard library, too, and there are plenty of other cases – in Base and outside of it – where macros could be improved via this syntax.
Activity