Strangely Consistent

Musings about programming, Perl 6, and programming Perl 6

Macros: nesting macros

Some features that the program author wants to implement need to straddle more than one macro. A common relationship between macros seems to be the outside-inside relationship.

AngularJS has this feature. When you declare directives (which can then act as elements or attributes in your application HTML), one directive can declare that it needs another one around it to function using the require option. See this example from UI Bootstrap, with <tabset> being the outer directive, and <tab> the inner.

There are two important parts of this feature:

(Further down in that example, there's even a tabHeadingTransclude directive which nests inside tab. That is, a directive can be both a child and a parent. Though the tabHeadingTransclude is so simple that it only requires the inclusion for the "validity" reason above, not for "sharing".)

Meanwhile, in Perl 6 macros

I believe this feature is something that macro authors will want, and find useful. I think macros will end up working in groups like this sometimes, and by far the most common way to group things will be the parent/child relation. (Or ancestor/descendant, to be exact. Hm. Some things will want to be tightly nested with no stuff in between parent and child; other things will be more lenient.)

One example that's already in Perl 6: given and when.

given $food {
    when /pie/ { say "mm, pie" }
    default { say "waiter, could you send in some pie?" }
}

Actually, what Perl 6 requires in this case is that the when (and default) find itself lexically inside a topicalizer block, not necessarily a given. So this is fine:

for @foods {
    when /pie/ { say "mm, pie" }
    default { say "waiter, could you send in some pie?" }
}

sub review-food($_) {
    when /pie/ { say "mm, pie" }
    default { say "waiter, could you send in some pie?" }
}

Which indicates that in some cases, the child macro might want to specify that it wants to be the child of (semantically) an any junction of parent macros.

Note that this example does not extend to the following likely parent/child constructs, which are too dynamic in nature and therefore the domain of the runtime rather than the compiler.

How about a more DSL-y example?

I went hunting for a good example of this, preferably one that exists already. I guess my DSL advent post is one. It does illustrate both the "validity" and "sharing" benefits. But I feel I need another example.

Let's imagine a DSL for making database transactions.

transact $conn {
    # do some queries
    # change stuff around in several steps
    rollback
        unless $success1;
    # more changes
    rollback
        unless $success2;
    commit;  # this would probably be optional at the end, though
}

I like this example more than the one in the advent post, because the parent macro transact and the child macros rollback and commit are collaborating on a type of data very central to the language itself: control flow. In the sense that we want a commit or rollback to also exit the transact block.

That makes the example feel real to me. Likely the mechanism for this would be transact setting up a custom handler, and commit and rollback throwing (control) exceptions with different cargo.

This type of examples inhabits a Goldilocks zone where the macros have to be not-too-simple (because then a frothy mix of subs, dynamic variables, and exception handling would work), but also not too much like a proper language (because then slangs would rush in and soak up the use cases). I think any more complicated than this and it'd be a slang. In fact, I don't mind if there's a nice, sliding scale, so that you can essentially evolve a cluster of macros of this type into a slang if you want.

Grammars do it bottom-up

I don't know what to make of the fact that in our Perl 6 grammars we've ended up with a solution where

Parsing is different from macros, so maybe it's fine. But by current design, macros can neither reach downwards to their macro children or upwards to their macro parents. And I just find it a bit odd that the design I find natural with macros (children declaring their need for/communication with parents) runs counter to the design we find useful in grammars (parents grabbing information from children by default).

I notice that I am confused. ☺

Implementation

Remember, "generate, analyze, and typecheck". The thing I'm suggesting here falls under "analyze", because we mainly want to introspect/read the program structure, and communicate things across it. Maybe the "validation" requirement falls under "typecheck".

Anyway, I fully expect there to be a general framework through which macros could do this the "hard way":

(Hm. Will parent macros therefore leave a detectable trace of themselves in the Q-tree? Probably. I half-expected macros to desugar into more primitive types of nodes. But maybe parent macros are an exception, since they want to be found. Or they desugar to something primitive, like a block, but it's marked up in a standard way, with a once I was a macro symbol.)

What I'm proposing here is basically just sugar, for people writing the macros, to set up this relationship between parent and child the "easy way":

macro transact($conn, $block) { # TTIAR, but see separate post
    # ...
}

macro commit() is inside(&transact) { #`[...] }
macro rollback() is inside(&transact) { #`[...] }

This is enough to declare the relation between the macros. There also needs to be a mechanism to get the object representing the actual callsite of the macro call. That's the one in our example that would hold $conn. In the worst case, we could fall back on asking about this through a namespace or global object somehow. There may be a cuter/saner way that I'm missing right now. Either way, it's possible.

Not addressed by this proposal

Identified in a previous post.