public
Created

Musings on macros

  • Download Gist
macro-musings.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
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 '&macro()' 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.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.