Skip to content

Pattern matching for expressions #12102

Closed
@MikeInnes

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    julepJulia Enhancement Proposal

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions