Exam in lab, Thursday, March 5th

Scope and Closures

Scope

(Section 3.3)

The textual region of a program where a binding is active

Scope

Can also be used to refer to a region of a program where no bindings are changing

  • Blocks e.g. { … }
  • Functions
  • Modules

Referencing Environment

The set of active bindings

Inspecting the Referencing Environment

import inspect

def main():
  a = 1

  def sub(b):
    c = 3
    print('Locals:', inspect.currentframe().f_locals)
    print('Parent:', inspect.currentframe().f_back.f_locals)

  sub(2)

main()
Locals: {'c': 3, 'b': 2}
Parent: {'sub': <function main.<locals>.sub at 0x7f89903ce268>, 'a': 1}

Static (Lexical) Scope

Binding can be determined at compile time

Early Basic

  • Global-only scope
  • No variable declarations
  • Variable names could be only 2 characters (letter + digit)
10 FOR X = 1 TO 10
20 PRINT "HELLO, WORLD"
30 NEXT X

Fortran 77

  • Global and subroutine scope
  • Subroutines can’t be nested
  • Variable declarations are optional (assumed to be integer if name begins with I-N or real otherwise)

static variables

  • Local variable with lifetime covering entire program execution
  • aka save in Fortran, own in Algol
/* `static` example in C */

#include <stdio.h>

void count() {
  int value = 0; // Try with `static`
  printf("%d\n", value++);
}

int main(void) {
  for (int i = 0; i<10; i++) { count(); }
}

Nested Subroutines

  • Introduced in Algol 60
  • Allow scopes to nest

Closest Nested Scope Rule

  • A name is known in its scope and in nested scopes unless it is hidden by another name in a nested scope
#include <stdio.h>

int main(void) {
  char* level = "Grandparent";
  {
    printf("%s\n", level);
    {
      char* level = "Local";

      printf("%s\n", level);
    }
  }
}

Built-ins

  • Many languages define built-ins at an outermost scope level
  • These can be overwritten in certain languages
# Don't do this...
def len(str):
  return 42

print(len("Hello, World"))

Declaration Order

  • Some languages require the definition exist prior to it being accessed in a block
  • Some languages require all definitions to be present at the start of the block
#include <stdio.h>

int main(void) {
  printf("%d\n", i); // Error

  int i;
}

Section 3.5

Overloading

Some names can refer to multiple objects at a given time

Polymorphism

Allows a subroutine to perform differently based on the types involved

#include <stdio.h>

void print(int val) {
  printf("%d\n", val);
}

void print(char* val) {
  printf("%s\n", val);
}

int main() {
  print((char*)"Hello, world!");
  print((int)10);
}

Modules

  • Provide a way to bundle object such as subroutines, variables, and classes together
  • May not be available unless explicitly exported from one module and imported into another

Python Module Example

Section 3.6

First-class Functions

  • Functions themselves can be used just like any other value
  • They can be passed as a parameter, returned from a function, etc
def call_twice(fn):
  fn()
  fn()

def say_hello():
  print("Hello, world!")

call_twice(say_hello)

Lambda Expressions

  • Also called anonymous functions, as they may not have a name
nums = [1,2,3,4,5,6,7,8,9,10]

squares = map(lambda n: n*n, nums)

print(list(squares))

Shallow and Deep Binding

  • Shallow binding - A referencing environment is bound when the function is finally called
  • Deep binding - A referencing environment is bound when the reference is created

Closures

  • A function and its referencing environment created when a reference to a function is created
def pair(first, second):
  return lambda idx: second if idx else first

p = pair(7,32)

print(p(0))
print(p(1))

import inspect
print(inspect.getclosurevars(p))