This post is a follow-up of something I've written almost one year ago, that I 
have summarized here:
http://d.puremagic.com/issues/show_bug.cgi?id=5007

Feel free to ignore this post if you are busy, or if you didn't appreciate the 
precedent post.


In my C/D programs some common troubles (or even bugs) are caused by the 
scoping of variable/constant names: (1) wrongly thinking I am using a local 
name while I am using a name from an outer scope, or (2) using an outer scope 
name while I wrongly think I'm using a local name.

Outer scopes are struct/class attributes, global variables, or 
variabiles/constants defined in the outer function when you are working in an 
inner function.

Given the frequency of troubles such mistakes cause me, I don't understand why 
newer languages don't try to improve this situation with stricter visibility 
rules. (In D the visibility situation is more complex than C because there are 
structs/classes and there are inner functions too, so I think just copying C 
rules isn't enough. There are even inner classes, despite I have used them only 
once in D, to port some Java code to D. I think they are useful mostly to port 
Java code to D).

In Python3 the default is the opposite one, and it seems safer (this 
explanation about Python is not the whole story, but it's a good enough 
approximation for this discussion): in a function if you want to use a variable 
name that't not defined locally you have to use the "nonlocal" (from Python2 
there is also "global" keyword for similar usage, but it's not good enough if 
you want to use inner functions):

# program#1
def foo():
    x = 10
    def bar():
        nonlocal x
        x += 1


Some examples of hiding outer names in D this (is shows just the first half of 
the problem):

// program#2
int i;
void foo(int i) {} // hiding global variable
struct Bar {
    int j, w;
    static int k;
    void spam(int j) { // hiding instance variable
        int w; // hiding instance variable
    }
    void baz(int k) { // hiding static variable
        void inner(int k) {} // hiding outer variable
    }
    static void red() {
        int k; // hiding static variable
    }
}
void main() {}


You can't adopt the Python solution in D. But doing the opposite is possible, 
the idea is disallowing hiding present in outer scopes (so they generate an 
error) unless you mark the variable with something, like a "local":


// program#3
int i;
void foo(local int i) {}
struct Bar {
    int j, w;
    static int k;
    void spam(int local j) {
        local int w;
    }
    void baz(local int k) {
        void inner(local int k) {}
    }
    static void red() {
        local int k;
    }
}
void main() {}


Time ago I have even thought about an @outer attribute, that is optional and 
it's more similar to the Python3 "nonlocal". If you add @outer at a function 
signature, you have to list all the names from outer scopes you will use inside 
the current function (and if you don't use them all it's an error again). In 
SPARK there is something similar, but more verbose and it's not optional (I 
don't remember if the Splint lint has something similar):


// program#4
int i = 1;
int j = 2;
@outer(out i, in j) void foo(int x) {
  i = x + j;
}
struct Foo {
  int x;
  @outer(in x, inout j) void bar() {
    j += x;
  }
}
void main() {}


"local" is more light to use, in syntax too, while @outer() is more heavy but 
it's more precise (but to make it work I think "local" can't be optional). Of 
the two ideas I like @outer() better. @outer is meant to avoid bugs but I think 
it's usable in debugging too, when you find a bug that I think is scope-related 
I add some @outer() to make the code more strict and make the bug stand out.

In theory with some more static introspection (like a __traits that given a 
function/method name, returns an array of variables used inside it (or used by 
functions called by the function), and how they are used, if read, written or 
both) it's possible to add an user-defined attribute like @outer with user code.


In bug 5007 Nick Sabalausky has shown a simpler idea:

// program#5
int globalVar;
class Foo() {
    int instanceVar;
    static int classVar;

    @explicitLookup // Name subject to change
    void bar() {

        int globalVar;   // Error
        int instanceVar; // Error
        int classVar;    // Error

        globalVar   = 1; // Error
        instanceVar = 1; // Error
        classVar    = 1; // Error

        .globalVar       = 1; // Ok
        this.instanceVar = 1; // Ok
        Foo.classVar     = 1; // Ok
    }
}

Bye,
bearophile

Reply via email to