-
Notifications
You must be signed in to change notification settings - Fork 14
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
Statement/expression mismatch and quasi scoping #7
Comments
I forgot to emphasize that the "make blocks return values" solution is one that will work, even with all the listed drawbacks. The only alternative solution I've been able to come up with is to say "scopes shouldn't be rooted in blocks the way they are now". That is, an inserted AST fragment can have its very own scope without having its own block. (Note that this incidentally solves the I don't know which approach would be best. They seem to each have their drawbacks. |
A third solution, and the one I dislike the least up until now: Introduce a new More properties of
By way of silly example, this code:
Would result in the for loop being macro-injected like this:
And when run, this would print: Note well that:
Other advantages, touching on what's been kvetched above:
All of the benefits, none of the nasty side effects! |
This necessitates the stuttering of an expression into a statement. Thanks to the tentative conclusion in #7, I expect this stuttering to be temporary. Also change the first test in t/syntax/quasi.t to expect something slightly different from (the AST returned from) a quasi. I'm not sure we'll keep that output at all, but for now it's easier just to change the test to expect what it actually gets.
This necessitates the stuttering of an expression into a statement. Thanks to the tentative conclusion in #7, I expect this stuttering to be temporary. Also change the first test in t/syntax/quasi.t to expect something slightly different from (the AST returned from) a quasi. I'm not sure we'll keep that output at all, but for now it's easier just to change the test to expect what it actually gets.
This necessitates the stuttering of an expression into a statement. Thanks to the tentative conclusion in #7, I expect this stuttering to be temporary. Also change the first test in t/syntax/quasi.t to expect something slightly different from (the AST returned from) a quasi. I'm not sure we'll keep that output at all, but for now it's easier just to change the test to expect what it actually gets.
The former is now the strange weird thing generated as a result of evaluating a quasi. Thanks for the naming suggestion, #7! I first tried making Q::Expr::Block a subrole of both Q::Block and Q::Expr, but that ended in hilarity, tragedy, and horror. First I got a number of diamond-type conflicts with methods from Q, and after uselessly fixing these, I got this wonderful error: No concretization found for Q So for better or worse, Q::Expr::Block is currently a Q::Block but not a Q::Expr.
I've been thinking about this recently. I think we want the very simplest semantics possible, which to me seems to be this:
|
70d2b9c begins to address this issue. I'd say by the time we can actually return the value of the last expression statement out of a |
Try doing this: $ bin/007 --backend=i13n examples/name.007 Should get this output: # static frame #1 # call A from line zero: frame 2 # static frame #3 # call B from line zero: frame 4 1. macro name(expr) { # static frame #5 # call C from line zero: frame 6 2. if expr ~~ Q::Postfix::Property { 3. expr = expr.property; 4. } # static frame #7 # call D from line zero: frame 8 5. if expr !~~ Q::Identifier { 6. throw new Exception { 7. message: "Cannot turn a " ~ type(expr) ~ " into a name" 8. }; 9. } # static frame #9 # call E from line zero: frame 10 # call F from line zero: frame 11 10. return quasi { expr.name }; 11. } 12. 13. my info = { 14. foo: "Bond", 15. bar: { 16. baz: "James Bond" 17. }, 18. }; 19. 20. say(name(info)); # info 21. say(name(info.foo)); # foo 22. say(name(info.bar.baz)); # baz
A bit of a design issue more than an implementation one.
The thing to keep in mind when reading this is that in 007, statements can contain expressions, but expressions cannot really contain statements. That's to say, of course a block term will hold statements in it (Update: block terms don't exist anymore) (Update-update: but sub terms do), but from the point of view of the expression, that block is opaque. More to the point, there's none of Perl 6's
do
keyword in 007. (do
turns the subsequent statement into something that can be used as an expression.)A macro call is by necessity (part of) an expression (Update: this is too limiting an assumption). But sometimes we want to insert something that isn't just an expression: a whole statement, a sequence of statements, a block, a subroutine declaration, a macro declaration, etc.
In fact, I bet the case where we insert statement-level stuff rather than expression-level stuff is the common case.
I had a solution for this. It can be summarized in this table:
Inserting an expression into an expression is fine. Even inserting an expression into part of an expression is fine. (Mainly because this is what expressions do all day.) It even works out pretty well with precedence and stuff by default. Better than C's macros, anyway.
If a statement of basically any kind ends up at the top level of an expression, then that's an error condition in the AST and we have to do something. It's in this case that we "stutter" and simply peel away the outer
Q::Statement::Expr
, letting the inner statement break free of it as if from a chrysalis.(We have to do a similar peeling-away process for the first two cases, too, because the result from the macro will be a block with a statement with the expression we want to insert, and that block and that statement have to go away.)
It's only the last case, trying to insert a statement-y thing in the middle of an expression, that should be an error.
Now all of the above was fine, and is doable. But here's the snag, and the reason I'm writing this down.
In the fullness of time — and hopefully not too soon — the above 007 program should output "5", because of how scoping and macro insertion works.
But by what I said above, all that would be inserted into the
say(...)
expression would be the expressionx
, a textual identifier without any context associated with it. The result would be that "19" would be printed. (Or, worse, if I hadn't declared that outerx
, a "variable undeclared" error at parse-time (hopefully) or runtime (in the worst case).)Perl 6 solves this in the following way: what's inserted is always a block, which gets run as it gets evaluated. Blocks, blocks, blocks. Even in the middle of an expression. The beauty of this is that the block can be forced to have the appropriate
OUTER
(the macro), and the variable lookup would work out. (As has been noted elsewhere, this makes the unhygienic lookup more problematic. But let's not worry about that now.)Now,
I think that last one actually works. But it feels very wrong. I thought we would be able to do macro insertion with proper scoping with the primitives that we have, and without resorting to generating a lot of extra stuff just to make everything fit together.
Just some random thoughts before going to bed. I suspect we're missing a primitive somewhere, or something. In the best case, the whole thing can be solved by allowing blocks to return the value of the last statement. (I think it can.) But that opens up a smallish can of worms that I had kept closed by doing it the way he have it right now (blocks always evaluate to
None
):return
return from subs (and macros). Slightly higher conceptual burden all around.do
keyword. On the other hand, if we said that conditionals and loops always evaluate toNone
, then that's an exception in the language.my
statement? Of aconstant
statement? Of asub
declaration? Again this takes us down the slippery slope of mixing up statements and expressions.The text was updated successfully, but these errors were encountered: