People coming in from a Common Lisp tradition seem to have difficulties accepting the way Perl 6 quasi
works, to the point where they argue that it shouldn't be called quasi-quoting.
We can let this code stand as an example of how Perl 6 macros work:
macro moo($arg) {
my $x = "macro";
return quasi {
say $x;
say {{{$arg}}};
}
}
my $x = "mainline";
moo $x; # prints "macromainline"
I don't claim to know a whole lot about Lisp macros, despite having studied them for a while now. My lack of knowledge extends to actual practical use of macros as well as their exact origins. Books that I consider important and have failed to peruse as much as I'd like so far include "On Lisp", "Lisp in Small Pieces", and "Let Over Lambda".
I explain this because I know that much of the confusion stems from differences in expectations, terminology, and usage. Bridging this is hard, but essential to even making sense across languages. One reason this happens is that since macros are in a sense "becoming part of the program-building process", language design decisions about how programs are built enter in early and influence how macros work.
My goal with this document is to explain some of the Perl 6 design decisions, how they influence macros in Perl 6, and how they lead to quasi
ASTs having lexical scope.
...is not covered in this gist.
I've found it useful to think of three types of AST objects floating around in Perl 6:
- synthetic ASTs, put together by building individual AST node objects and stringing them together
- macro argument ASTs, turned from inert program matter into AST values by dint of occurring as arguments (or operands) of a macro call
quasi
ASTs, created whenever the program contains aquasi
block
Synthetic ASTs lack a lexical context. This is because they were never embedded in the program in the first place, so there's no context to have in the first place.
Macro argument ASTs have a lexical context because they're ordinary code, and ordinary code has a lexical context. That, plus the expectation that macro substitutions in Perl 6 be hygienic, so they tend to preserve the context they have.
Which still leaves open whether quasi
ASTs should have them or not. Should they be more like synthetic ASTs, or ordinary code?
Let's approach that question by talking about what the alternatives are.
Refining the original example to an even smaller one, which seems to capture what upsets some Lisp expectations:
macro moo() {
my $x = "whee!";
return quasi {
say $x;
}
}
To someone used from Lisp quasi-quoting, it's highly surprising/shocking that $x
resolves inside the quasi
block! In Lisp, a quasiquotation is mostly quoted code, which isn't run or evaluated in any way until it's injected into the program. The "quasi" part of "quasiquotation" allows for some bits to nevertheless resolve from the surrounding scope, so that the quoted code can get useful bits of surrounding context. But (crucially), that resolution always happens at the discretion of the quasiquoter.
In Perl 6, there is no such thing as "inert code". Either you put the code in a string or a comment — and then it's arguably not code at all — or you allow the parser to do a preliminary pass over it, with all that implies in terms of symbol resolution and missing-symbol detection. Compared to Lisp, this level of quotation is very weak. We'll come back to why it's necessary in the next section.
There is a sense in which the code is still more inert than ordinary non-quasi Perl 6 code: the code does not get "anchored" in its current position in the source code. Instead a quasi
can be passed around and eventually injected at another place in the program. This is what macros use.
There is also a sense in which the "quasi" is justified: unlike ordinary code, unquotes ({{{ }}}
) can appear in a structured way inside a quasi
. I maintain that these do the moral equivalent of Lisp quasi-quoting. In Lisp, everything is list, an AST, a program fragment, all at the same time. In Perl 6, we can't quite do that, but we can require that only ASTs ever be passed into an unquote. (Here, too, hygiene applies.) Similarly, in Lisp, anything can go basically anywhere, which is a great strength and flexibility. In Perl 6, we can "type" the unquotes by giving them a grammatical category. Again, this turns out to be necessary in order to calm down the parser.
return quasi { say $x; }
When the parser sees the $x
in the quasi
block, why must it be so particular about whether the $x
resolves (at parse-time)? Why can't it just let it be and continue the parse?
I would argue that the Perl 6 parser is morally prevented from turning off its checks. This reason is twofold: first off, we want the quasi
block to be "a way to build an AST by writing ordinary code", and the checks happen in ordinary code. Secondly, the Perl 6 parser takes much of its core identity from the checks; they're part of the parse, not a bonus add-on.
The existence of something like no strict
shows that there are ways around this. But in the general case, parsing and symbol resolution are totally intertwined in Perl 6.
fee foo fie;
Assume that the above three terms resolve. Is the code valid Perl 6? It depends whether fee
is a sub or a class.
$ perl6 -e 'sub fee($) { say "OH HAI" }; sub foo($) {}; sub fie {}; fee foo fie'
OH HAI
$ perl6 -e 'class fee {}; sub foo($) {}; sub fie {}; fee foo fie'
===SORRY!=== Error while compiling -e
Two terms in a row
[...]
So, in the general case, context is indispensible for the Perl 6 parser. There's no such thing as a piece of Perl 6 code completely detached of its surrounding context. And so the quotation mechanism is less "inert" than in Lisp.
The Lisper might then be tempted to explaim, "well, in that case, (quasi-)quotation is simply not possible!". I would instead say "well, in that case, (quasi-)quotation has to abide by the normal parsing rules of Perl 6, which involve some checking and resolution".
Another tempting thought might be: "OK, so we must have a context. Let's just set that context to NULL and see what happens".
This is what happens. You can no longer use say
. This is a built-in that comes from the setting, which is normally found somewhere up the lexical lookup chain. I'm pretty sure people will want say
in their quasiquotes, and would complain if it's not there.
None of the built-in subs would be in place, nor any of the built-in types Int
, Str
, Regex
, etc.
Worse: no operators. At least nominally, operators are also declared in the setting. Because we don't have the setting in our context, we no longer have any of the operators. It's hardly worth talking about Perl as Perl without the operators. I'm not sure what would happen, since the parser might still parse the operators, but then simply not be able to resolve them to anything.
There are a couple of other things we could set the context to — the mainline scope where the macro gets injected, the lexical scope just outside of the macro, always the global scope — but they all seem to me to be strictly worse than just saying "a quasi is a block, expect it to behave like one".
[...]