Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kill off Q::Unquote #334

Open
masak opened this issue Jul 13, 2018 · 6 comments
Open

Kill off Q::Unquote #334

masak opened this issue Jul 13, 2018 · 6 comments

Comments

@masak
Copy link
Owner

masak commented Jul 13, 2018

Unquotes aren't a type of AST node. They're a type of nothing, an un-thing, a nonexistence, like Dementors or The Nothing or whatever's outside the wall in Mario Kart.

The exact reasoning is described here: one, two, three, four, five. But tl;dr: a quasi is less like an AST fragment and more like a function/lambda expression containing instructions for how to build an AST. The parameters are the expressions in the unquotes.

Since we know that this is the model we have to converge on, we should switch 007 over to it, the sooner the better.

@masak
Copy link
Owner Author

masak commented Aug 7, 2018

I'll just pop in and say that it's not obvious how we should do this. Needs more design.

@masak
Copy link
Owner Author

masak commented Oct 4, 2018

Here's a first stab at a design: if a quasi is meant to become a function (taking as inputs the concrete epressions to fill the unquotes with and returning as output a complete Qtree) then to a first approximation that function should:

  • Contain all the "fixed" (non-unquoted) parts of the tree as Qtree fragments.
  • Put fixed things and unquote expressions together in the right way.

Most of this is straightforward. There is one special case that's... a little bit tricky — operators and precedence. I think the easy way to say this is that we don't have enough information, in the general case to build a tree out of the terms and operators in an expression, if it contains an operator unquote. The missing operator contains vital information about precedence and associativity that might determine the final shape of the expression tree.

If the above paragraph sounds like it contains a lot of hedging, it's because I really haven't thought this through all the way. But the simplest way forward seems to me to be to store expressions in a kind of "intermediate" form in the quasi function, and to do the precedence calculation only after splicing in the operator unquotes. For simplicity, I suggest we do this for all expressions at first. Later we can optimize that if we feel it matters. In practice it shouldn't make a noticeable difference unless maybe the number of macro expansions is enormous.

@eritain
Copy link

eritain commented Oct 29, 2018

The missing operator contains vital information about precedence and associativity that might determine the final shape of the expression tree.

I might be treading into premature optimization territory here, or just demonstrating the limits of my own imagination. But anyway, I can imagine uses for macros that can return one of several expressions, depending on arguments they're called with. I can imagine uses for macros that can be called with one of several operators. I'm failing to imagine circumstances where:

  • you want to return one of several expressions
  • and the expression tree shapes you want to return each correspond to one or more of the operator precedences and associativities
  • and you take an operator as an argument
  • and the precedence of that operator invariably is one that corresponds to the expression tree shape you need
  • even when that operator was defined after you wrote your macro
    • by a person who has never met you or the macro's user
    • and even if the definition just takes an existing operator and gives it different precedence
  • and the text of the quasi surrounding the operator unquote doesn't have to be obnoxiously clever (read: unmaintainable) in order to be parseable in all and only the right ways
  • and all of the macro's users are sure to receive a spooky-action warning
    • before they pass an operator to it
    • with enough documentation that they can tell what code the macro will return
    • and yet it's still worth their time to use this crazy macro instead of writing/generating special-purpose code themselves
    • and document the reasons for their maintainer's sake

"Oh, yeah, um, this whole thing can straight-up parse differently depending on the precedence and associativity of the operator you give it. ... Of course it works correctly with every possible precedence level and associativity direction. We wrote exhaustive unit tests!"

So, I'm really content with the possibility that a quasi might only be able to return one expression tree shape, and that macro authors who need several shapes will have to write several quasis and a switch statement. "Pluripotent stem quasis" might turn out to be the costly and unnecessary offspring of a Perl 5 source filter and an SQL injection attack.

@masak
Copy link
Owner Author

masak commented Oct 29, 2018

Thank you, that's a good bit of feedback. I need to let it simmer for a while.

For now, two things:

  • It's not clear to me that anyone is helped by the extreme late-binding of precedence/associativity we're enabling here. It's possible everyone would be more helped by us categorically deciding that (say) unquoted operators are always maximally tight and left-associative. I'd go that way right now if I was more sure that the arbitrariness of that decision would be offset by its ease-of-use.

  • One slightly relieving detail is that it is easy for the macro author to explicitly "override" any late-bound precedence: just add parentheses for grouping. Either around the (a {{{op}}} b) to make op maximally tight, or around the (...a) {{{op}}} (b...) to make op maximally loose.

@vendethiel
Copy link
Collaborator

This is the issue people usually have with C macros (#define F(A, B) (A)+(B)) and it's making me a bit uncomfortable.
At least I feel comfortable saying that a {{{op}}} b should mean (a) {{{op}}} (b) in that, if a is 3 * 4 and b is 5 + 6 and op is /, the code should mean (3 * 4) / (5 + 6) (by virtue of an AST being a tree).

@masak
Copy link
Owner Author

masak commented Oct 29, 2018

@vendethiel That makes a lot of sense. I will chalk that up as an argument for making unquoted operators maximally loose. I like the appeal to the AST being a tree.

No-one would be happier than me if we ended up implementing a model that's radically simpler than extreme late-binding of unquoted operator precedence. So I will definitely be inclined to listen to arguments for simplifying the model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants