Lecture 6

Binding

An important idea in programming is the notion of binding. In general, binding means associating two things, such as a variable with a value or a function name with a function.

Binding can occur at different times in the programming process:

  • Go values like true, false, and nil were bound at design-time by the language designers.
  • A value like int in C can be bound at implementation time, i.e. the exact number of bits in a C int can be set through the compiler.
  • A constant value could be bound to a value at compile-time.
  • Variables are often bound at run-time, e.g. when you read a string from a file and store that string in a variable, the variable is being bound to the string at run-time.
  • A function in a library, such as printf in C, is bound at link time. A linker is, essentially, a program that “links” function calls to their code. Typically, the code is in a libary.

A static binding is a binding that occurs before run-time and is unchanged during run-time. If a binding can change during run-time, it is called a dynamic binding.

Variables can be bound to types either explicitly or implicitly. For example:

double x = 3.14;  // C: explicit

auto x = 3.14;    // C++: implicit (type inference)

x := 3.14         // Go: implicit (type inference)

In all three of these examples, x is bound to a floating-point type at compile-time. That is, the compiler determines the type of x.

Another example of implicit type binding is FORTRAN, which, by default, implicitly declares variables beginning with I, J, K, L, M, or N to be Integer types, and all other variables to be Real types. This kind of implicit typing has not been very popular. One of the problems is that if you accidentally forget to declare a variable’s type, FORTRAN may automatically assign it the wrong type. Modern FORTRAN lets you use an Implicit none statement to turn off this behaviour.

Dynamic Type Binding

Some languages, such as Python, Ruby, JavaScript, and PHP, the type of a variable is not specified by a type declaration (or some other naming convention). So in Python you can write code like this:

x = 5
x = "house"

The variable x is not restricted to contain only variables of a particular type. This makes Python (and languages like it) very flexible.

This examples shows another use of dynamic type binding:

>>> def twice(x): return x + x
>>> twice(5)
10
>>> twice("house")
'househouse'

The twice function works with any value that is defined for +. There’s no need to specify the type of x ahead of time.

Ruby is an interesting language for many reasons. All Ruby values are objects, and so Ruby variables don’t have different types: Ruby variables are all of the same object type. Thus any value can be assigned to any Ruby variable.

There are at least two problems with dynamically typed languages. One problem is that they can suffer from the errors that compilers might easily catch in statically typed languages. In a dynamically typed language, such errors might crash the program at run-time, which hurts their robustness. The second major problem is performance: dynamic type binding requires type-checking to be done at run-time (instead of compile-time), and this extra checking can slow down programs significantly.

Variable Lifetime

The lifetime of a variable is the time the variable is bound to a memory cell to the time it is unbound. We distinguish three lifetime categories:

  • Static. Static variables are bound to memory cells before program execution. This can make them quite efficient, but also less flexible since you can’t typically add new static variables at run-time (and so, for instance, recursion is impossible in a language with only static variables).

  • Stack-dynamic. Stack-dynamic variables are variables instantiated at run-time, usually on the program’s stack. For example, in languages like C++, Java, and C#, local variables in a function or method are, by default, stack-dynamic. They are instantiated when their declaration statement is reached, and de-allocated when the function ends.

    For example:

    int abs(int a, int b) {   // Java
          int diff = a - b;
          if (diff < 0)
                  return -diff;
          else
                  return diff;
    }
    

    a, b, and diff are pushed onto the program stack each time abs is called. They are popped when it ends.

  • Heap-dynamic. Heap-dynamic variables are variables whose memory is explicitly allocated and de-allocated at run-time. For example, in C++:

        int* ip = new int;    // new int allocates a new int memory cell on the heap
                              // ip holds the address of this cell
    
    *ip = 4;
    cout << *ip << endl;  // prints 4
    
    delete ip;            // de-allocate the memory cell ip points to
    

    In many languages, the deallocation of heap values is done automatically by a garbage collector. The reason for this is that it turns out to be surprisingly challenging to call delete at just the right time in complex progrtams. Call delete too soon and you’ve destroyed a value your program needs; call delete too late, and you’ve wasted memory (e.g. caused a memory leak).

Scope

A variable’s scope is the range of statements over which it is visible. A local variable is a variable whose scope is restricted to a block of code. A nonlocal variable is visible outside of the block in which it was declared. Global variables are a kind of nonlocal variable.

Many modern languages are statically (or, lexically) scoped. This means that the scope of the variable can be determined before the program runs. Static scoping helps programmers to read source code and figure out its type without running it.

Here’s an example of static scoping from JavaScript:

function big() {
        function sub1() {
                var x = 7;   // hides the x defined in big
                sub2();
        }

        function sub2() {
                var y = x;      // which x does this refer to?
        }
        var x = 3;
        sub1();
}

Under static scoping rules, when x is seen in sub2, you first check to see if it is declared in sub2. It’s not, so you then check to see if x is declared anywhere in it parent function (big). Indeed it is, so the x in sub2 is the one declared in big.

C-based languages let you create blocks that introduce new scopes. For example, in C++ you can write code like this:

void f() {
        int x = 5;
        cout << x << endl;   // prints 5
        {
                int x = 7;
                cout << x << endl;   // prints 7
        }
        cout << x << endl;   // prints 5
}

This example has two blocks, one nested inside the other. Blocks are not often used on their own like this, but instead tend to be used as the body for functions, if-statements, loops, etc.

Another interesting example of scoping arises in functional languages, such as LISP, Haskell, and ML. For example, in Clojure (a LISP-based language), you can write this code:

(let [x 4
      y x]
  (+ x y))

This is a let-expression, and it starts by binding 1 to x and y. You can then use x and y in the body of let, but nowhere outside of it. The entire expression evaluates to 8. Also, x and y cannot be changed once they’re assigned: they are constants.

Dynamic Scoping

Dynamic scoping is an alternative to static scoping that has largely fallen out of favor. Most examples of dynamic scoping occur in older languages, such as APL, SNOBOL, and LISP (at least early versions). Perl lets you optionally declare variables that follow dynamic scoping rules.

The idea of dynamic scoping is that the meaning of a variable depends upon the value of the most recent variable with the same name in the current function call stack (as opposed to the enclosing block of source code).

Here’s some sample code (from http://msujaws.wordpress.com/2011/05/03/static- vs-dynamic-scoping/) that gives different results if you use dyanmic or static scoping:

const int b = 5;

int foo()
{
   int a = b + 5;  // What is b?
   return a;
}

int bar()
{
   int b = 2;
   return foo();
}

int main()
{
   foo(); // returns 10 for static scoping; 10 for dynamic scoping
   bar(); // returns 10 for static scoping; 7 for dynamic scoping
}

The idea of dynamic scoping is that when the value of a variable is needed, the program searches for variables with that name among the names of variables in functions that are currently active. So in this code, when foo() is called inside bar(), the it’s the value of the local b in bar that gets used in b + 5.

In general, dynamic scoping is unpopular because it makes it harder to reason about the meaning of programs from their source code alone. Under dynamic scoping, you can’t always tell for sure what a variable refers to until the code runs, because the order in which functions are called matters.

Another problem with dynamic scoping is that it exposes the local variables of a function to other functions, thus allow the possibility that they could be modified.

Also, dynamic scoping has performance and type-checking difficulties. Probably the major good thing about dynamic scoping is that it is easier to implement than static scoping.