Skip to content

Instantly share code, notes, and snippets.

@masak

masak/why.md Secret

Created November 14, 2015 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save masak/978d6e7c0ad131da0334 to your computer and use it in GitHub Desktop.
Save masak/978d6e7c0ad131da0334 to your computer and use it in GitHub Desktop.
Why Perl 6 quasi ASTs seem to need a lexical scope

The reason this document exists

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 "macro␤mainline␤"

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.

The suitability of the word "quasi"

...is not covered in this gist.

Taxonomy

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 a quasi 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.

Quasi blocks are blocks

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.

Why must the parser care so much?

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".

Just assume no context at all

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".

Summary

[...]

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