Strangely Consistent

Theory, practice, and languages, braided together

Lexpads and why roles need fixups

We need a solution that makes us need less vodka. — jnthn

There are many extremely simple and elegant software solutions out there. But there are also those special moments, when you realize that something is more complex than you thought, and that the complexity is most likely essential.

Character encodings are the prototypical example for me. Certainly datetime handling qualifies as well.

Reaching the realization that there is that extra essential complexity, comes (at least for me) with a sinking feeling as I get used to the idea of living with that complexity forever.

With me so far? Something seemed quite easy, wrapped up, ready to go home for the day, but then all this extra complexity rears its head. And it's never going away.

I started writing this blog post because I realized that a certain snag in role handling in Rakudo doesn't have a URL, and it really should. So, without fanfare, here's the situation:

my $x;

role R {
    method foo {
        say $x;
    }
}

class C does R {
}

$x = "OH HAI";
C.new.foo;

I think we all agree that this should print OH HAI. Good? Good. Nothing up my sleeve, no hidden mirrors or escape hatches — it does print OH HAI. Relax. Take a deep breath.

Ready? Because after you learn this, there's no going back. The world will forever be more complicated and, with some luck, you'll be having that sinking feeling.

Ok, so. Just a few simple facts:

Let's recap what we know by applying it to the code. There's the variable $x. We know we will find it in the static lexpad of the mainline, because it's declared on the top level and everything has a static lexpad. Does it also have a runtime lexpad? Yes, it does, because the mainline starts running after compilation is over. Will we find $x in several runtime lexpads? No, only the one.

Now, we ask ourselves the question: which lexpad is C.foo referring to?

"Of course, it's the runtime lexpad", we reply, innocent to the fact that the trap has already shut around us and there's no way out. See, it has to be the runtime lexpad, because the sane thing for the program to do is to print OH HAI, and that value is certainly stored in the runtime lexpad.

But no. It's not possible. It can't. There's no way. Because roles are created at compile time, before there is a runtime lexpad! The role method has no choice: it's bound to the static lexpad, because at that point, that's all there is.

And there we are. The trap has now closed. There's no way to both (a) do what the user expects, and (b) keep the internal model nice and free of weird exceptions.

Since we like (a), we ditch (b) and create an exception in Rakudo. It's called a fixup, it's installed during role creation, and it makes sure that whenever the block surrounding the role is entered, the role rebinds its OUTER to that block's fresh lexpad.

Simple it ain't. Nor is it pretty. But it makes the user happy.

The reason I started thinking about this is that we run into the same kind of problem with macros, and the same kind of fixup will probably be needed there.

More to the point, at the point where this need-for-a-fixup starts showing up in different parts of the architecture, it's time to give it a name and perhaps think of a uniform way to address this. That's where jnthn's quote from the start of the post originates — we need a solution that isn't worse than the problem, and that we can reason about without having to scale the Ballmer peak.