Skip to content

Instantly share code, notes, and snippets.

@masak

masak/post.md Secret

Last active December 4, 2018 21:34
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 masak/9d586206067f1cfbae31e5e79588a1ce to your computer and use it in GitHub Desktop.
Save masak/9d586206067f1cfbae31e5e79588a1ce to your computer and use it in GitHub Desktop.
Advent post: Day 5 - Variables

Day 5 - Variables

Such a simple thing, isn't it? A variable is a name that holds a value.

Occasionally, the value it's holding might be replaced by a different one — hence the name. (According to the surgeon general, variables that don't experience change on a regular basis should see their physician, and ask to be diagnosed as constants.)

Even though they're very easy to grasp, and basically every language has them, my goal today is to convince you that variables are actually wonderfully tricky. In a good way! I aim to make you stumble out of this blog post, dazed, mumbling "I thought I knew variables, but I really had no idea...".

Towards the end, the experimental language 007 will also figure, because it's completely and utterly this language's fault that I'm thinking so much about variables.

Left or right?

The first way variables are odd is that they are used in two completely different ways.

my $x = "Christmas";

say("Merry " ~ $x);       # reading
$x = "Easter";            # writing

Sometimes we use variables to read a value, and sometimes we use them to write a value. But in both cases, the syntax looks exactly the same! Some older languages (such as Forth) actually have different syntaxes for these two usages, and I love them for it. But such a convention does not appear to have survived into the modern era.

Instead, we distinguish the two uses by grammatical location. If you're on the left side of an assignment, you're being written to. Otherwise, you're being read from.

In the literature, these two uses are called lvalues and rvalues, respectively. For "left" and "right", respectively.

Rvalues are pretty normal, and correspond to how we think about variables in general; they just evaluate to the value that they contain. Lvalues, however, are weird. They're more like boxes that you can put something in (or memory locations? references?), or if not the box itself, then the detached ability to put something in it. If lvalues had a type, it would look something like (T) -> void, something that accepts a T but doesn't return anything.

Parameters

Variables are entirely essential to modern programming. There's also a principle stating that they are entirely unnecessary.

That's right! Tennent's Correspondence Principle! (I know what you're thinking. No, not that Tennant.

This principle is mainly pointing to a way to rewrite all our variable declarations in our programs so they're parameter declarations instead. One example should be enough to show the general principle:

# Before
my $veggie = "potato";
say "$veggie, and that's all I have to say about that!";

# After
(-> $veggie {
    say "$veggie, and that's all I have to say about that!";
})("potato");

See how the variable declaration turns into a parameter declaration, and the corresponding assignment turns into an argument? Experienced (or should I say war-torn) JavaScript developers recognize this construction as an IIFE.

Since we can always do this transformation, we don't really need variables. Only parameters. I'm mainly telling you this so that you can be a little bit extra grateful that you don't have to write your code with just parameters.

Final note about Tennent's Correspondence Principle: its original use is described briefly on Wikipedia. It was basically forgotten until Java was about to get closures and its name was invoked and the principle was overused a bit, maybe.

Dynamic scoping

In Perl 6, variables take on an extra "twigil" (an optional symbol after the sigil) whenever the variable scope deviates from lexical scope. The most important of these alternative scopes might just be the dynamic scope.

Again, we'd better show the difference with an example:

my $lexical = "mainline";
my $*dynamic = "mainline";

sub foo() {
    my $lexical = "foo";
    my $*dynamic = "foo";
    bar();
}

sub bar() {
    say $lexical;       # "mainline"
    say $*dynamic;      # "foo"
}

foo();

Forget about Virgo and Saggitarius and the other astrological signs. The only distinction worth making about your deeper personality is whether you're doing lexical lookup, or dynamic lookup. There are, after all, only two types of people.

Whether we like it or not, a lookup is a process. We're given a name, and we go look for the corresponding value. It's dispiriting, I know. But let's do it anyway, and see where it leads.

For $lexical, the lookup happens by looking at the program text itself. Is that variable defined inside the smallest scope we're in, that bar sub? (It's not.) Then we proceed outwards, to the immediately surrounding scope — which turns out to be the scope of the entire program. Is it defined there? Yes! What luck. We return from the lookup, victorious, with the value "mainline".

For $*dynamic — notice the little star in the name? I told you there was astrology involved! — we also start in the innermost scope, the bar sub, and look for a definition there. (We find none.) But now something different happens. We don't follow the block structure outwards, instead we follow the caller chain upwards. Who called us? foo. That's where we look. Is there a definition there? Yes! So we're done, and successful.

From a historical perspective, dynamic lookup was the "obvious" one, and most languages had it at first. Lexical lookup only gradually proved its worth, and is now endemic. Perl 5 actually straddles this history, and my variables are lexical, but the older our/package variables are dynamic. That's what you get from being around while history is happening.

In Perl 6, we also do our part by forbidding the term "parent scope". In a world with both lexical and dynamic lookup, it's just too confusing. Instead, we prefer the terms OUTER (for lexical lookup) and CALLER (for dynamic lookup).

Some constructs in Perl 6 (such as return and next) try to be lexical if possible, but fall back to being dynamic if they don't find any lexically surrounding thing to "attach" to. This type of behavior didn't really have an academic term, it seems, so the Perl 6 synopses call it "lexotic".

Variables in macros

Still with me? Goodie. Let's talk about macros.

use experimental :macros;

macro moo {
    my $counter = 0;
    quasi {
        say ++$counter;
    }
}

for ^10 {
    moo;
}

This is a simple macro which just injects the code say ++$counter into that for loop there. The program will print all the numbers from 1 to 10, on individual lines.

Great, but... how? Notice that the macro-expanded code refers to $counter, but lexical lookup (as described above) would not find the variable declared in a surrounding lexical scope. But still, this program works, or rather, is meant to work.

So what is the underlying principle that makes the program work. It turns out that by a very lucky happenstance, variables that are defined inside a macro body can be "uniquified" and replaced by an lvalue. The injected code say ++$counter actually looks more like say ++☐, where the gets to represent that (unrepresentable-as-code) lvalue.

I know this is a small thing, but I was so happy when I finally put this together. In fact, I was so happy that I wrote it down as a github issue, just to make sure the details all work out. Stay tuned for an implementation of this and, consequently, hygiene.

(For those keeping score at home, hygienic macros are milestone D3 in this grant application.)

Just to be clear — this is more like an intent to implement. Full hygiene is yet to come in Perl 6 (and 007). But it feels heartening to have a clear path forward.

Anyway, that's variables. They're lovely, a little bit weird, but in the end we're happy they are there. Happy advent. ☺

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment