Variable Lifetime And Scoping

Local Variable Scoping

Scilab's visibility rule for locally defined variables follow those of block structured languages: Variables local to a block shadow all variables of the same name not local this this block.

Variable v "shadows" variable v' means that v' is not accessible neither for reading nor for writing. What is available for manipulation is variable v.

Example 2-3. Shadowing of local variables

        -->deff('y = foo(x)', 'a = 2*x, y = a + 1')
        -->a = 1.0 // top level
         a  =
            1.  
        -->foo(3.5)
         ans  =
            8.  
        -->a
         a  =
            1.  
        -->foo(a)
         ans  =
            3.  
        -->a
         a  =
            1.  
    

Example 2-3 demonstrates that the variable a which is local to function foo has no influence on the variable a in the surrounding environment. Even calling foo with a variable named a does not break this rule.

As usual in block structured languages variables from all enclosing scopes can be accessed, unless they are shadowed. Example 2-4 shows usage of variable a from an enclosing scope.

Example 2-4. Accessing variables from the enclosing scope

        -->deff('y = bar(x)', 'y = a + 1')
        -->a = 1 // top level
         a  =
            1.  
        -->bar(3.5)
         ans  =
            2.  
        -->bar(-1)
         ans  =
            2.  
        -->a = 2
         a  =
            2.  
        -->bar(-1)
         ans  =
            3.  
    

Now what is the "enclosing scope"? It is the call stack; Scilab scopes dynamically!

Example 2-5. Dynamic scoping

        // scoping in Scilab
        deff('first_local()', 'x = ""foo"", second()');
        deff('first()', 'second()');
        deff('second()', 'disp(x)');

        x = 1;
        first_local()                   // prints 'foo'
        first()                         // prints 1
    

Example 2-5 deserves a close look. Dynamic scoping can be confusing for people used to e.g. C's lexically scoped auto variables.

/* lexical scoping in C */

void first_local(void);
void first(void);
void second(void);

int x = 1;

int
main(void)
{

    first_local();              /* prints 1 */
    first();                    /* prints 1 */

    return 0;
}


void first_local(void)
{
    int x = 123;                /* warning: unused variable `x' */
    second();
}

void first(void)
{
    second();
}

void second(void)
{
    printf("%d\n", x);
}

But compare to Perl[1]:

# dynamical scoping with Perl's local variables

sub first_local {
    local $x = 'foo';
    second();
}

sub first {
    second();
}

sub second {
    print "$x\n";
}

$x = 1;
first_local();                  # prints 'foo'
first();                        # prints 1

Dynamic scoping is an inherently dangerous feature for it might not be obvious where a variable gets its value.

Let us look at functions which try to change variables from an enclosing scope.

    -->deff('y = baz(x)', 'a = 2*a, y = a + 1')
    -->a = 3 // top level
     a  =
        3.  
    -->baz(1)
     ans  =
        7.  
    -->baz(1)
     ans  =
        7.  
    -->a
     a  =
        3.  

Obviously, a is unchanged by the calls to baz. What happens is the following:

  1. A local variable named a is created, and the contents of variable a from the enclosing scope is copied into it. Within baz the local a is changed.

  2. When the thread of control leaves baz the previous value of a is restored.

In other words: A local variable cannot influence a variable of the same name in any enclosing scope. The only ways to "export" a – possibly modified – value is either via the list of return values (the preferred way), or with a global variable.

As strange as this may sound to programmers accustomed to languages that require an explicit declaration of all variables, this is a necessary feature in Scilab as variables are created when they are first written to (e.g. as in Python). If a local variable in a function would change a global variable or local variable of the same name in an other function, adding a new function to an existing system or library became a maintenance nightmare.

Global Variables

The global attribute of a variable var is often misunderstood. It does not place var in a all-encompassing name space so that it could be accessed from everywhere without further ado. Instead, global places the variable var in a separate name space; separate from the interpreter's name space and separate from all local functions' name spaces. — and this is only the first half of the story.

    -->v = -1
     v  =
      - 1.

    -->global('v')

    -->who('global')
     ans  =
     v

    -->clear v

    -->who('global')
     ans  =
     v

    -->deff('y = useglobal()', 'y = v')

    -->useglobal()
     !--error 4
    undefined variable : v
    at line 2 of function useglobal called by :
    useglobal()

As promised, this is only one half. After saying global var the variable lives in its new name space, but it cannot be accessed. To work with it it must be imported explicitely, using the global again. Therefore, a slightly modified version of useglobal works.

    -->deff('y = useglobal2()', 'global v, y = v')

    -->useglobal2()
     ans  =
      - 1.

    -->v = 1 + 2*%i
     v  =
        1. + 2.i

    -->useglobal2()
     ans  =
      - 1.

Now what if we want to access v from the interpreter level again? It must be imported just as it must be imported into any function.

    -->global('v')

    -->v
     v  =
      - 1.

    -->v = 17 + 4
     v  =
        21.

    -->clear v

    -->useglobal2()
     ans  =
        21.

One last hint: global variables even "survive" a restart. If this is not desired, clearglobal should be called in the user's Scilab startup file, ~/.scilab.

    clearglobal()

will clear all global variables.

Clearing Variables

During everyday programming it is not necessary to explicitely remove variables from the work space. All local variables of a function die on exit from that function anyhow, and the variables in the global name space usually do not need special treatment.

However, there are conditions under which it is preferable to wipe out a variable completely. This happens if you need to avoid a pollution of the name space while working with the list of all variables, e.g. who('local'). The correct command to kill variable v is

    clear v

Note that there are no parentheses. The assignment

    v = []

sets v to the empty matrix. It does not remove the variable from the workspace.

Global variables are cleared with clearglobal.

There is no need to worry if you do not understand how and why to kill a variable. This feature is only needed in very rare occasions.

Notes

[1]

The behavior of the C-example is reproduced by replacing local with my.