Skip to content

Instantly share code, notes, and snippets.

@vendethiel
Last active December 19, 2017 21:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vendethiel/ca0ea82f29d288da8ab0e848bc6d16bb to your computer and use it in GitHub Desktop.
Save vendethiel/ca0ea82f29d288da8ab0e848bc6d16bb to your computer and use it in GitHub Desktop.

Hi there!

Please allow me, on this day of the advent calendar, a small tangent. I’m not going to be talking directly about a cool Perl 6 feature of idiom. Instead, I’ll open a small window on what could be – and hopefully will be at some point!

If you, like me, have been following the progress on Rakudo for a few years, you have seen this bit regularly in the releases:

Some of the not-quite-there features include:

  • advanced macros

Well, what does that exactly mean? Perl 6 does have macros, but they are currently limited beyond what people usually want to do. It’s not that they’re currently useless – they are still useful, from other posts in previous years of the advent showcasing them, to OO::Monitor using macros to report typos earlier.

Enter 007. 007 is a "Small experimental language with a license to macro”. What does this mean?! It’s a language that’s been created to temper and experiments with macros, so that their design is ready and battle-tested when they get integrated into Perl 6.

So, what’s in it? 007 tries to mimic the “powerful" parts of Perl 6, so that we don’t design macros for a completely different language. That means phasers, infix operators, (the gist of) a MOP and regexes are in.

What does it look like? At its core, 007 is meant to like Perl 6. It does, however, shed some parts of it. Let's take a look at the most important snippet you'd want to write: FizzBuzz.

my n = 1;
while n <= 100 {
    if n %% 15 {
        say("FizzBuzz");
    }
    else if n %% 3 {
        say("Fizz");
    }
    else if n %% 5 {
        say("Buzz");
    }
    else {
        say(n);
    }
    n = n + 1;
}

hat? You don’t care about that? Well, obviously. I did promise you macros. We’re going to take a look at a simple macro, “name”, that returns the name of either the object of the last index.

macro name(expr) {
    if expr ~~ Q::Postfix::Property {
        expr = expr.property;
    }
    if expr !~~ Q::Identifier {
        throw new Exception {
            message: "Cannot turn a " ~ type(expr) ~ " into a name"
        };
    }
    return quasi { expr.name };
}

my info = {
    foo: "Bond",
    bar: {
        baz: "James Bond"
    },
};

say(name(info));           # info
say(name(info.foo));       # foo
say(name(info.bar.baz));   # baz

So, you're probably going "WAT" right here. You're right – this gist is missing some explanations. One of the most important feature of macro is access to the AST (Abstract Syntax Tree). Macros need to be able to mess with the code's structure (like in Lisp), not the code text (like in C). The Q:: types are standardized types that represent the program's shape. They do not particularly need to represent how the compiler/interpreter thinks about the code, but they need to be stable because we are writing our code – our macros – against this introspection API.

In this code sample, we use two Q types: Q::Postfix::Property, which represents the dot access, and Q::Identifier, which represents an identifier. First, we check if we have a property. If that's the case, we extract what's on the right side of the dot (remember, a.b.c is (a.b).c). We then check if we do end up with an identifier (and not, say, a number), and print that. This is e.g. how we could implement C#'s nameof operator, without having to add anything to the language!

A few days ago, masak++ published a blog post titled Has it been three years?, which marked the 3rd birthday of 007. While some areas are still (very) rough, it looks more and more like an usable language, and the possibilities progress day after day.

The next thing we are looking at is implementing is parsed. Here's how it could look: (this example works in a PR, but uses special-casing right now):

macro statement:<whoa>() is parsed(/"whoa!"/) {
		return quasi @ Q::Statement {
				say("whoa!");
		}
};

whoa!;

This is also probably how we'd want them to look in Perl 6... Or not? The discussion is still open! You are encouraged to join the bikesh... discussion :-). The language is still young and needs a lot of fleshing out, as much for its advanced features as for its simpler features.

Before I leave you with your appriopriate amount of meta-fun, here's a milestone 007 wants to reach that's currently sitting in a branch: implement infix:<ff> as part of the library (if you're unsure, the Perl 6 docs on ff apply here), and not as part of the language. Here goes the code!

# our infix macro takes a lhs (left hand side) and a rhs (right hand side).
macro infix:<ff>(lhs, rhs) is tighter(infix:<=>) {
    my active = False; # our current value when starting
    return quasi {
        if {{{lhs}}} {
            active = True; # if the bit on the left is true, we switch to active mode
        }
        my result = active; # the result we are returning
        if {{{rhs}}} {
            active = False; # if the bit on the right is true, we switch to inactive mode
        }
        result; # return the result stored *before* the rhs ran.
    };
}

my values = ["A", "B", "A", "B", "A"];
for values -> v {
    if v == "B" ff v == "B" {
        say(v);
    }
    else {
        say("x");
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment