Home CPSC 401

Bindings & Scope

Variables

A variable is an abstraction of a memory cell. They have the following characteristics:


Binding

A binding is an association between an entity and an attribute, such as between a variable and its type or value, or between a function and its code.

Binding time is the point at which a binding takes place. There are different times a binding can happen:

Any binding that happens at run time is called dynamic, and any that happens before run time is called static. Things that are statically bound can not change as a program runs, and those that are dynamically bound can change.

What bindings take place in the following C code?


count = count + 5;

What about the following Python code?


count = count + 5

Static Type Binding

Static type binding can be done either implicitly or explicitly. Explicit declaration means that the programmer must specify the types of variables. This is done in languages like C, C++, and Java.

The code below has a few type annotations:


int fact(int x) {
  if(x == 0) {
    return 1;
  } else {
    return x * fact(x - 1);
  }
}

Type declarations can become quite verbose when using templates or object-oriented programming:


vector<int>::iterator i = nums.begin();

Implicit static typing means that the programmer does not need to declare types. In languages with type inferencing, the types are still statically bound, but it is done by the compiler. These languages also normally provide the ability to declare types if necessary. Type inferencing can lead to difficult compiler errors if the programmer makes a type error.

Below is the factorial function in Haskell which uses type inference:


fact x =
  if x == 0 then
    1
  else
    x * fact (x - 1)

What are some benefits and drawbacks of type inference?


Dynamic Type Binding

Dynamic type binding is when the type of a variable is not decided until the program runs. Dynamically-bound types are always implicit.

Below is the factorial function in Python which uses dynamic binding:


def fact(x):
  if x == 0:
    return 1
  else:
    return x * fact(x - 1)

Dynamic typing can lead to run time errors that a compiler would have caught. Consider the following Python code that attempts to add the numbers from from 1 to 100:


total = 0 
number = 1 

while number <= 100:
  toal = total + number
  number = number + 1 

print(total)

This code prints "0" instead of "5050". This type of error can be difficult to debug.

Consider the following Python code:


def getValue(which):
  if which:
    return 10
  else:
    return "oops"


number = getValue(which) + 1 
print(number)

This code will run correctly if "which" is true. However, if "which" is false, the function will return a different type and produce a type error.

What are the trade-offs between static and dynamic typing?


Storage

When a program runs, variables need some memory space to hold their values. Allocation is when a variable is bound to a memory location and deallocation is when it is unbound.

The time between is called the lifetime of the variable.

There are three basic ways a variable can be allocated:

In Java, built in types are stack dynamic while all objects are heap dynamic.

In C#, built in types and "struct" objects are stack dynamic while "class" objects are heap dynamic.


Scope

The scope of a variable is the portion of code over which it is accessible.

Local variables are those whose scope is limited to a single function.

Global variables have a scope of the entire program.

What are the scope and lifetime of "x" in this code?


int function() {
  static int x = 0;
  x++;
  return x;
}

In most languages, there can be multiple variables with the same name in a given scope. What does the following code print?

int y = 10;

void function() {
  int y = 20;
  printf("%d", y);
}

This is called "shadowing". In languages without declarations, this can lead to subtle issues as in this code:


g = 10

def function():
  g = 1 

function()
print(g)

This code would be fixed with the "global" keyword:


g = 10

def function():
  global g                                                                                                                       
  g = 1 

function()
print(g)

There are two ways of determining the scope of variables: static and dynamic.


Static Scope

Static scope is based on the code of the program. This means that scope can be determined just by looking at the code.

When a variable is referenced, it is searched for in the current bolck, then the enclosing block, on and on, until the global scope is reached.


int x = 10;

void f() {
  int x = 5;

  if(x > 0) {
    printf("%d", x);    
  }
}

Many languages allow nested functions:


def function1(y):
  def function2(x):
    return x + 1
  return function2(y)

In this code, function2 itself is local to function1 and can only be called inside of that function.

All common languages use static scoping.


Dynamic Scope

With dynamic scoping, scope is based on the call sequence of the running program. This means you cannot determine which variables are in scope by looking at the code.

References to variables are searched through the stack.


int x = 10;

void f() {
  printf("%d", x);
}

void g() {
  int x = 20;
  f();
}

int main() {
  g();
}

What will this code print using static scoping?

What about dynamic scoping?

LISP was originally dynamically scoped and some variants still are.

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.