-
Notifications
You must be signed in to change notification settings - Fork 15
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
Alma's (and Raku's) quasi scoping is significantly different from Scheme's/Lisp's #567
Comments
Heh, vendethiel's view on this is apparently the "shifty eyes" emoji. Fair enough. |
Five years ago I kvetched about the unquotes in Raku not being able to "nest" the way they can in Lisp. That is, you can nest quasis just fine, but there's no such thing as a nested unquote. I just realized two things:
|
Also, looking with fresh eyes at @vendethiel's
Going to open up a separate issue about adding a nested unquote to the test suite, just to have it in there. That's going to require the |
More so "I need some rest before looking at this properly" :-). (Which means of course that four and a half in the morning is a fitting time to answer... Also, it's been too long since we last talked!). It does resonate with me, though. I think this was my biggest hurdle when trying to understand Raku macros. I think the reason there's not that much to read on the difference is because macros have mostly been implemented in late-bound languages.
Non-hygienic lisps and hygienic lisps have wildly different stories here. The ones that are hygienic have a smaller ability to reach "outer" names like Raku/Alma, but it might also mean they require less unquoting (scheme templates). |
Yes. 🎉👯♂️🚀 Hi! 🦄😎🤗 I think I "burned out" there on Alma for a bit — a lot of the obvious next steps with the language are big huge undertakings the size of #242. (They are listed here, by the way.) I am still on a bit of a quest to Make Bel Go Fast, but hoping to be able to put that aside and come back to Alma's Phase II sometime Soon™. I haven't forgotten the walker we started building — I think that one is not far from being practically useful.
This is something I too have understood better lately. The whole subfield of Denotational Semantics seems to be about researchers asking "how does it work?" and then answering that question by implementing an interpreter for the semantics. Much of my Raku/Alma's "static resolution" mechanism is the left-shiftable part of regular runtime lookup. The "lexical" of "lexical scoping" already hints that this is possible: that there is a part of the scoping that's evident from the program text itself, and hence early/static.
I still need to understand this better. At least lately I've actually been starting to read and process/classify all the macro/hygiene papers I've collected over the years. It's brain-bashingly hard, but also rewarding.
🙀 |
TimToady's restriction: if an outer The quasis-are-lambdas view: A quasi represents "an AST with holes", but the concept of holes is fundamentally a negative-space concept — instead of thinking of holes as a Qtype, we should think of the whole quasi as being a lambda-expression/anonymous function for building a complete AST where the holes correspond to parameters. Getting a concrete AST from the quasi happens by invoking the lambda-expression, passing an argument (the expression inside of an unquote) for each parameter. The lambda-expression approach should be read as a conceptual abstraction — on the implementation level, the quasi can be code-generated into something like an anonymous function. But it's not first-class and cannot be passed around or otherwise observed from userspace — any attempt to evaluate the quasi just returns the result of calling the anonymous function. A lambda-expression is sufficient but not necessary, I guess. I take this to be what S04 talks about with "When is a closure not a closure". All this skirts around the question of what the AST representation of a quasi should be. As metaprogrammers, we are morbidly interested in having an AST representation of code in general and quasis in particular, to analyze and transform. Saying that unquotes are a non-Qtype then constitutes a bit of a non-answer, or at least an unhelpful/unsatisfactory answer. Assume we have this Alma code (with
At compile-time So I think This is how far my insufficient brain will carry me. What I can't get it to answer is — what does this all mean if quasis are lambdas? The quasi in a quasi would be a lambda in a lambda, surely. But the double This comment ran away from me a bit. I was going to end up with a demonstration of how |
Closing. Maybe the summary is this: quasiquotes in Scheme are more "quote", and those in Alma/Raku are more "quasi". |
I just learned from this blog post that the whole "code objects are closures" thing is discussed in Quasiquotation in Lisp (1999) by Alan Bawden.
|
It's starting to make less and less sense to me, in the context of Alma and Raku. "unquote" is about splicing an element in, sure, but if it were only that we could get away with a I think |
@vendethiel How marvellous — I had thought about this too, and come to diametrically opposite conclusions. Very briefly, because I don't have the headspace right now:
Um, I don't know if TimToady would consider |
They do. That's why I'm unsure double unquote is as important as it is in i.e. Common Lisp. I'm not even sure you absolutely need unquote at all, except for syntactic purposes (though auto-unquote of an ast would feel very magical). |
Scala macros and Scala's LMS both have auto-unquote, to great effect. But my personal conclusion is that they get away with this because their macros are operating on type-checked ASTs, after type checking/inference. If you know that It's a neat idea, and I'm definitely jealous. Then again, I don't think Raku (or Alma) would be willing to pay the price involved in pushing types that deeply into macros/quotation. |
Well, you could make it magical but it feels a bit wrong. Scheme templates have no "normal values" so they can auto-unquote (and they don't have an explicit unquote mechanism). Still, I'm unsure what use there is for |
I like where we ended up in this issue. I just found another "data point" of sorts. In Everything old is new again: Quoted domain specific languages, Philip Wadler talks about using quasiquotation to build a DSL for LINQ-like queries, which are then optimized and sent to a database engine. Around the 23-minute mark, though, he mentions a "use case" for quasiquotation and unquoting that we haven't brought up: using a variable Three things, in no particular order:
Apparently this (quote-unquote-quote) works in MetaOCaml. I should try MetaOCaml. |
This is wrong, although past-masak is making a mistake here that is very easy to make with macro calls. I don't believe it has a name, but if it did, it should be something like "confusing AST with values".
|
Just found this, which unnerves me slightly:
I wonder if this is connected to masak/bel#427 — not sure, but it might well be. Edit: A later comment also feels insightful:
I think that those two identified cases are really good to use as litmus tests/test cases to think through in order to evaluate one's quasiquote design. |
(This issue is a pretext to say hi to @vendethiel. Hi! 👋)
(Issue already resolved and will be closed as effectively WONTFIX in a week or so from its creation. Discussion still encouraged.)
To summarize:
In Lisp and Scheme, even when things are nice and lexically scoped, the notion of scope actualizes very late, at evaluation-time. Specifically, it does not exist as part of read-time.
In Raku and Alma, scoping is the concern of the parser. That is, the parser is interested not just in parsing a variable/identifier, but also in making sure that it's bound to something in scope.
To take the smallest possible example that highlights this difference:
The string
"foo"
reads just fine into a symbolfoo
in any Lisp or Scheme, but when you try to evaluate the result, you get a lookup error, because the environment doesn't bindfoo
.The string
"$foo"
doesn't parse in Raku, and the string"foo"
doesn't parse in Alma. Both parsers will throwX::Undeclared
. You would get AST nodes corresponding to a variable lookup if you didn't get the error — but that's a bit like saying that you'd have been in time for work if you weren't in jail for going twice the speed limit.Maybe one way to say this is that the Raku/Alma parsers emit not just an AST, but a scoped AST. This is not an established term at all, at least I don't think so, but it gets the point across. Raku and Alma's parsers emit scoped ASTs, which in practice means that they have already done an initial preprocessing of the AST, a kind of abstract interpretation or partial evaluation where all the static lookups have already been made.
(As I was typing out this issue, I also learned about abstract binding trees (Edit: changed link), which seems to be a different name for scoped ASTs. It's in Bob Harper's book Practical Foundations for Programming Languages, which I have not read. But it's good to know someone else has had the same idea.) (Edit: There's some concrete implementation information about ABTs in this PFPL supplement, section 3, "Elaboration" (which seems to mean "make an ABT from an AST").)
What's more, this difference "spreads" to quasis. In Raku/Alma, quasis are closures — quite orthogonally from their role as a data encoding of source code. This flows quite naturally from the above design decisions.
The same is not true in Lisp/Scheme. That is to say, a quasiquote containing
foo
is valid Lisp/Scheme, no matter whetherfoo
was defined or not in the context surrounding the quasiquote. In fact, pointing to the surrounding context feels a little bit random and unrelated! Meanwhile, in Raku,quasi { $foo }
is definitely a compile-time error if$foo
was not defined outside the quasi; and in Alma, dittoquasi { foo }
.Quoth S06:
"Resolve" is a nice word — it's like a runtime lookup, except that it's static, so what we're looking up isn't a value, it's more like a binder/definition site. A quasi does not have to occur in a macro definition (just like a
when
doesn't have to occur in agiven
), but we get the point being made.Also S06:
I'm not sure I agree that it should provide a warning. "Very likely" is not the same as "definitely"; see here for an example in Alma where I do this quite deliberately, and wouldn't want the warning. (Spec still has a point; it's likely a mistake and a kind of "level mixup". I do that all the time. But Raku as a language tends to be forgiving rather than forbidding in cases of late-bound stuff like this — cf. method calls that are guaranteed to fail at runtime, or return types on routines that conflict with the actual returned value; all of them late-bound and late errors.)
Also, an interesting bit from S06:
I don't know in which sense "hygienic" is invoked here, but "lexical scoping" is easy to interpret. This is the phrase that means that quasis have their own block scope, as has been discussed in #30 (somewhere...).
Anyway, I find it interesting — Lisp and Scheme choose one "obvious" design alternative, where the object code and the meta code don't share any lexical scoping. Raku and Alma choose the other path: they're nested lexically, because that's what it looks like they should be doing.
I wonder if a hidden kind of prior art is the way eval works in Perl 5 — there, we also get lexical-lookup errors at compile time.
(Later edit: I also wonder if it's kind of a timing issue, with lexical scoping itself. Quotation is from the very beginnings of Lisp; quasiquotation from the, uh, mid-70s, and lexical scoping got going somewhere around that time as well. It's possible quasiquotation (a) was mostly based on quoting, which doesn't have a notion of lexical scope at all, and (b) was also early enough to be mostly outside of the light-cone of lexical scoping, so it didn't occur to people as an idea at all. This is all assuming that it even needs an explanation in the Lisp world — when you quote something, it's data. It gets read but not processed or otherwise given any meaning before being evaluated.)
What's your view on this, @vendethiel? I know you've hinted at this difference a few times. I think I finally understand it better now. It's quite an obvious difference, even though neither Scheme nor Raku seems to call it out as one.
The text was updated successfully, but these errors were encountered: