Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow access to the COMPILING:: variable scope in the macro's caller #313

Open
masak opened this issue Jun 21, 2018 · 7 comments
Open

Allow access to the COMPILING:: variable scope in the macro's caller #313

masak opened this issue Jun 21, 2018 · 7 comments

Comments

@masak
Copy link
Owner

masak commented Jun 21, 2018

In #207 (comment) we detail a need to sometimes declare variables in the mainline scope where the macro was called.

Here, let me rewrite the naïve implementation into something that does the right thing using this requested feature — an example speaks a thousand words, after all:

macro infix:<ff>(lhs, rhs) {
    my active = new Symbol { name: "active" };
    my COMPILING::[active] = False;
    return quasi {
        if {{{lhs}}} {
            COMPILING::[active] = True;
        }
        my result = COMPILING::[active];
        if {{{rhs}}} {
            COMPILING::[active] = False;
        }
        result;
    };
}

My initial thought was not the above syntax at all, but a builtin (say, compilingScope()) which gave you access to an object with three methods:

  • declareVar (which would run Runtime.pm's declare-var method under the hood)
  • getVar, ditto
  • putVar, ditto

But the COMPILING:: syntax gets us much closer to what S06 wants, and has the added advantage that we see clearly that it's "regular" variables we're dealing with, so we're declaring, reading, and writing them the regular way.

The [active] syntax is part of the name (see #250), and is the way for us to interpolate a symbol into a longer name. It's inspired by ES6's "computed property key" syntax. In Perl 6, it'd be written as $::("COMPILING::{$active}") or some such. The thing that goes inside the [] can be a symbol or a string, but only symbols give that special warm gensym buzz.

I guess for consistency we should also allow my [someSymbol]; to work. Though offhand I don't see a use case for declaring variables with symbols in your own scope.

@masak
Copy link
Owner Author

masak commented Jul 1, 2018

It bothers me a bit that I had to invent the ::[..] syntax just to do this. Lexical variables are nice and simple, and this complicates them quite a bit by making the variable name dynamic.

I believe this code preserves the simplicity of lexicals while achieving the same effect:

macro infix:<ff>(lhs, rhs) {
    my active = new Symbol { name: "active" };
    return quasi {
        my COMPILING::{{{active}}};
        once {
            COMPILING::{{{active}}} = False;
        }
        if {{{lhs}}} {
            COMPILING::{{{active}}} = True;
        }
        my result = COMPILING::{{{active}}};
        if {{{rhs}}} {
            COMPILING::{{{active}}} = False;
        }
        result;
    };
}

Some notes:

  • The new variable COMPILING::{{{active}}} is now declared from inside the quasi block. That's fine, and has the same semantics. The declaration is only "executed" once, at injection-time, which installs exactly one variable in the mainline scope.

  • The once block can run several times, though, since once runs once per "clone" of the containing code object, that is, it runs anew for every frame. Just like we want.

  • Guess we need to write once as a macro. I'm not currently clever enough to realize whether we have the primitives available to express once as a macro in 007, or whether once itself needs to be such a primitive. (Update: See Implement the 'once' block macro #327.)

  • The unquotes rely on the assumption that we'll be able to pass symbols (or strings) into unquotes and be able to build bigger names/paths with them. I hope we'll be able to avoid adding Q::Identifier @ everywhere to those, but even if we have to, I still consider it cleaner and more minimal than the ::[..] syntax.

@masak
Copy link
Owner Author

masak commented Jul 1, 2018

Note that the once block is necessary there. If we removed it completely, we would return None instead of False sometimes out of the quasi.

If we put the initialization on the my statement (which I know feels tempting), then the COMPILING::{{{active}}} hidden variable would sponaneously switch to False on every evaluation of the ff.

I guess it's a little bit unexpected that the initializer of a my COMPILING:: will run more often than we get a fresh variable. In fact, that's a moderately strong argument for disallowing initializers on my COMPILING:: (Python-style), or maybe allowing them but giving them once semantics (Perl-style).

@masak
Copy link
Owner Author

masak commented Jul 3, 2018

...I still consider it cleaner and more minimal than the ::[..] syntax.

And good riddance, too. I realized later that having that syntax would've sold the static properties of lexical variables down the river.

@masak
Copy link
Owner Author

masak commented Jul 28, 2018

or maybe allowing them but giving them once semantics (Perl-style).

I've come around to this option being the nicest one. It's very Perlish and Strangely Consistent to do the right thing for the wrong reasons. 😉

@masak
Copy link
Owner Author

masak commented Jul 28, 2018

When don't you want symbols and hygiene in this case? My guess is "never". You never don't want that. Or nearly never, like 99% never.

So the next logical idea, inspired by Rust, is to use symbols by default in quasi blocks.

macro infix:<ff>(lhs, rhs) {
    return quasi {
        my COMPILING::active = False;
        if {{{lhs}}} {
            COMPILING::active = True;
        }
        my result = COMPILING::active;
        if {{{rhs}}} {
            COMPILING::active = False;
        }
        result;
    };
}

To be clear, we get to write the code that way, but the 007 compiler rewrites it as the version above with explicit usages of Symbol and the COMPILING::{{{active}}} syntax. I figure this is a case of the pit of success — if you use COMPILING::, and don't think of lexical hygiene, then lexical hygiene is what you get by default. You're welcome.

@masak
Copy link
Owner Author

masak commented Nov 19, 2018

my COMPILING::active = False;

Looking at this with fresh eyes, I find that confusing. Even I'm reading the code, going "then why wouldn't it get set to False every time the macro executes?" Which isn't the intended semantics.

The corresponding trick works with state in Perl 6, because the state declarator is a heads-up that something phaser-y is going on.

@masak
Copy link
Owner Author

masak commented Dec 16, 2020

The corresponding trick works with state in Perl 6, because the state declarator is a heads-up that something phaser-y is going on.

So let's use the state declarator too for this, no?

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

No branches or pull requests

1 participant