It would be really cool if one could do something like
macro m(expr::ExprHeadType{:call,:(=)})
f(expr)
end
macro m(expr::ExprHeadType{:block})
g(expr)
end
which would be equivalent to
macro m(expr)
if expr.head ∈ [:call, :(=)]
return f(expr)
elseif expr.head ∈ [:block]
return g(expr)
else
throw(ArgumentError("You gave me an expression I don't recognize."))
end
end
(of course we should think of a better name than ExprHeadType). Of course, this would have to then require that the expr argument be an Expr rather than a Symbol or literal. Any macros that aren't written with ExprHeadType would behave normally.
Additionally, it would be nice to be able to dispatch functions on expression types (mainly for the purpose of helping macros), sort of like it's currently possible to do with specific values using Val. (I don't think that's a crazy extrapolation from current behavior, but I know very little about the compiler.)
Is this completely crazy from a compiler perspective, or something people might be interested in?
I'm pretty sure I remember this being brought up before and people thought it was a good idea. I also know there's been talk of "finalizing" our AST/Expr representation for 1.0 as well.
This would largely rely on changing the AST representation to a more type-based one, where instead of using Expr everywhere with different values for the .head field, we use different types to represent different syntax nodes with specific structures. This would be a fairly disruptive change, but if we're going to do it, before 1.0 is certainly the time to do so. Now that macros can do dispatch (it's real dispatch, btw, just on ASTs), it would also allow writing some transformations more concisely and conveniently.
Going further, there could even be fully typed trees, like:
ForExpr.iterator
IfExpr.condition
Going further, there could even be fully typed trees, like:
ForExpr.iterator
IfExpr.condition
I would be very worried that that sort of thing would make symbolic manipulation too complicated. You'd have to remember or look up the fields for every possible type of expression. I think using head and args makes it pretty easy to infer what the structure of an expression will look like, it just gets really annoying sometimes to have to write big ugly conditional constructs to identify the heads. (And it seems somehow "anti-Julian"!)
I can see how it might be an issue to iterate. Maybe a dict? IfExpr.args[:condition] for example
This is the kind of thing pattern matching is good for.
What I mean is that while dispatching on expression heads is somewhat useful, you need to be able to match on full tree structures to get any real benefit. Expression structures are also very flexible and dynamic, so they are not a great fit for the design assumptions of our multiple dispatch. This kind of programming really demands a feature that converts a series of tree patterns to a nest of if statements. Such an approach also makes it easier to change the representation of expressions, since you only need to change the pattern compiler.
I've found #12102 which seems to be a suggestion that pattern matching for expressions be included in Base. The result seems to be that the poster wrote the MacroTools package which seems to still be actively maintained.
I find it interesting that most of the discussion in that post is about users not necessarily being knowledgeable about the AST. Personally, I find that to be a very small issue, I simply use the REPL to check what the heads of various expressions are, and usually the args are pretty obvious. Actually writing the macro code once I know exactly what I need to do is what I'm still finding slightly awkward.
It seems that this has now effectively been implemented through Val, though this cannot directly be used in macro definitions.
Maybe I'm crazy but I could have sworn that at some point Val was not working for Symbols. However, now it does. My original example becomes
h(::Union{Type{Val{:call}},Type{Val{:(=)}}}, expr) = f(expr)
h(::Type{Val{:block}}, expr) = g(expr)
macro m(expr)
h(expr.head, expr)
end
It's not beautiful, but it's certainly an improvement.
Is there some simple way of using Val to allow macros declarations to behave as if they're dispatching on expression types?
Is there some simple way of using Val to allow macros declarations to behave as if they're dispatching on expression types?
Use a real pattern-matching library (like the previously mentioned MacroTools package)?
Most helpful comment
This would largely rely on changing the AST representation to a more type-based one, where instead of using
Expreverywhere with different values for the.headfield, we use different types to represent different syntax nodes with specific structures. This would be a fairly disruptive change, but if we're going to do it, before 1.0 is certainly the time to do so. Now that macros can do dispatch (it's real dispatch, btw, just on ASTs), it would also allow writing some transformations more concisely and conveniently.