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 144 145 146
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. Seems there's nothing to prevent them from working, though.
 
Observation: Type matching is certainly possible on a macro, but recall
that the parameters coming in are Perl6::AST::Node objects.
 
Observation: A builtin to evaluate a Perl6::AST::Node structure would
certainly be useful. Maybe just overload &eval?
 
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, and the ASTs for 40 and 2 get
spliced in and hang 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 fairly frequent use in quasi-quotes. But prefix:<`> is free, and it
has a Lisp precedent, so I'll would be fine with defining 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. Behind the
scenes, this kind of invocation also needs to turn the values of its
arguments into AST.
 
Observation: It says in the spec that macros are called at BEGIN time,
which means that macro definitions *must* be predefined (as opposed to
classes). Therefore, there cannot be mutually recursive macro calls, by
definition. (Well, not at parse time, anyway.)
 
Here's a slightly more elaborate, useful example of a macro:
 
macro metafor(Int $levels, &block) {
return quasi { {{{&block}}}() } if eval($levels) < 1;
return quasi {
for ^10 -> $x {
metafor({{{$levels}}} - 1, &block);
}
}
}
 
metafor(3, { say "OH HAI" });
 
(Code assumes the existence of &eval(Perl6::AST::Node).)
 
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: We haven't even begun to think about routines for cutting and
pasting ASTs so far. There should probably be some. Unless that's the
responsibility of a non-core module.
 
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), as well as 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 = 2; # $x in quasi scope
say $x;
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.