Created
August 16, 2011 11:51
-
-
Save masak/1148915 to your computer and use it in GitHub Desktop.
Musings on macros
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Just grepping for /macro/ in S06, I find this: | |
macro circumfix:«<!-- -->» ($text) is parsed / .*? / { "" } | |
Observation: a circumfix toggles the parser from expect-term mode to | |
expect-op mode. Reasonably, this goes for macros as well. Which means that | |
the parser would croak if the next thing after <!-- --> was a term. That | |
doesn't feel right to me. I call bogus on that example. | |
Observation: no-one has ever mentioned multi macros or proto macros, to | |
my knowledge. Is there anything that prevents them from existing? | |
Observation: It seems to me we could at least do a limited type matching | |
in macros, such that 'macro foo(Int $i) { ... }' could work on at least | |
literals. Or variables with a declared type. There seems to be a concept | |
'static type' involved, or something; the type that we can derive with | |
what the compiler can derive at parse time. If a call to a multi becomes | |
ambiguous (even though at runtime it wouldn't be), the compiler issues | |
an error. | |
Observation: 'temp' is called a macro in S06. It's only alluded what it's | |
a macro for. Would really like to see it explicitly spelled out. | |
Ok, here's the simplest macro I can think of. | |
macro four { | |
quasi { 4 } | |
} | |
say four; # 4 | |
It intercepts the macro call 'four' at parse-time and injects a '4' at | |
that point in the AST. | |
Here's another one. | |
macro add($a, $b) { | |
quasi { | |
{{{ $a }}} + {{{ $b }}} | |
} | |
} | |
say add 40, 2; # 42 | |
It intercepts 'add 40, 2', essentially makes a call to that macro, | |
which inserts an infix:<+> in the AST, 40 and 2 get spliced in and | |
hangs off of it. Subsequent optimizer steps might find it and do stuff | |
with the addition of two literals. | |
Observation: I find the {{{...}}} syntax to be heavy-handed considering | |
its use. But prefix:<`> is free, and it has a Lisp precedent, so I'll | |
probably define a language extension which allows that. | |
Observation: $?PARSER is mentioned once in S06 under macros, and once | |
in S29 when talking about &eval. Suspect that it's a pre-braid fossil. | |
Observation: I guess that a macro will have to return either an AST, | |
or something Cool. If it doesn't, it's an error. Maybe it's even an | |
error to return something Cool but non-Str. | |
Observation: The '¯o()' syntax means that macros have to be available | |
at runtime, to return an AST which gets compiled and run. | |
Here's a slightly more elaborate, useful example of a macro: | |
macro metafor(Int $levels, &block) { | |
return quasi { {{{&block}}}() } if $levels < 1; | |
return quasi { | |
for ^10 -> $x { | |
metafor({{{$levels}}} - 1, &block); | |
} | |
} | |
} | |
metafor(3, { say "OH HAI" }); | |
It intercepts the macro call at the end, recurses three times, creating | |
an AST which corresponds to this code: | |
for ^10 -> $x { | |
for ^10 -> $x { | |
for ^10 -> $x { | |
say "OH HAI"; | |
} | |
} | |
} | |
So it'll print "OH HAI\n" 1000 times. | |
Observation: Some care should be taken to make stack overflow errors due to | |
infinite macro recursions understandable to people. Macro recursion can | |
happen both inside and outside of a quasiquote. | |
Observation: I'm not sure when recursive macro calls outside of a quasi | |
are made. If it's right when it's parsed -- that's the immediate guess -- | |
it feels very much like a BEGIN block (execute this at parse time)... but | |
then not all multi variants of the macro may have been parsed yet. Hm, I | |
guess that's a problem with BEGIN blocks as well. | |
Observation: We haven't even begun to think about routines for managing ASTs | |
so far. There should probably be some. | |
Observation: In the above example I had a desire to access the whole stack of | |
created $x variables, but I see no easy way to do that. Suggestions welcome. | |
Observation: Syntax errors involving macros returning strings (rather than | |
ASTs) would be much improved by giving both the snippet of code as it appears | |
in the original source (which will point to where to fix it), and the snippet | |
of code that the macro generated (which will clarify what's wrong). Showing | |
only one will lead to one kind of confusion; showing only the other will lead | |
to another kind. | |
Here's an example of hygiene: | |
macro hygiene($x) { # $x in macro callee scope | |
quasi { | |
my $x; # $x in quasi scope | |
say my $x = 2; | |
say {{{$x}}}; | |
} | |
} | |
my $x = 1; # $x in macro caller scope | |
say $x; | |
hygiene(3); | |
It should output "1\n2\n3\n", and not complain about redeclarations of $x ('cus | |
there aren't any). | |
Observation: There are three different scopes involved in a macro: | |
injects into runs at | |
-------------------------------- | |
caller<-------+ runtime | |
callee | compile time ("caller's compile time = macro's runtime") | |
quasi---------+ runtime | |
The quasi scope can, and does, interact with the caller scope in various ways, | |
but there's a default layer of hygiene applied which makes the 'my $x' in the | |
quasi not collide with the 'my $x' in the (mainline) caller code above in the | |
example. | |
If you *want* them to collide, you use $COMPILING::x, as per S06. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment