y#+TITLE: Scope is Orthogonal to Type

If you spend all your time working in Python, by default you can't even detect basic typos in your code like

def foo(elephant):
  return elephnt + 2

So you need to spend all your time learning how to write correct code without a static type checker, partly by writing a much better test suite, by using a linter, etc.

-Julia Evans1

Scope is Not Type

Thinking type systems cause scope issues is a mistake. Type systems and scope are orthogonal. Just because a language has a dynamic type system doesn't mean every kind of static analysis has to be hard. To demonstrate this, I have two example programs below. Each is written in a dynamically typed language. This first program is in Python and will not crash until after a long calculation has been made.

from time import sleep

def calculate_large_prime():
  sleep(10)
  return 13

foo = 3
calculate_large_prime()
print foob

This second program is in Racket and has the same mistake as the Python program, but it won't compile at all. The mistake requires no testing to find.

#lang racket

(define (calculate-large-prime)
  (sleep 10)
  13)

(define foo 3)
(calculate-large-prime)
(displayln foob)

This isn't a special feature of Racket. Perl requires variables to be declared.2

my $foo = 3;

The difference between Python and these other languages is that Python variables have implicit scope. In Python, the syntax for assignment is the same as the syntax for variable declarations, so it is ambiguous what scope a binding exists in. This has nothing to do with Python's type system. Racket and Perl are both dynamically typed, but they can still detect typos at compile time because variables have to be declared first. If a variable is mispelled, the implementation will complain about a nonexistant variable. Implicit Scope Considered Harmful

Implicit scope can already turn simple typos into annoying runtime errors, but in Python it goes on to form three annoying special cases. To refer to a global variable inside of a function, Python programmer's must first mark the identifier with the 'global' keyword. If the identifier is not marked, then a new local variable will be created instead. The 'global' is a reverse declaration. It explicitly prevents a new variable from being created.

STATE = 'Fudge'

## This function is broken. It just creates a new
## local variable named STATE.
def set_state(new_val):
  'Change the global state to a new value.'
  STATE = new_val

## This function works. It explicitly refuses to
## create a new local variable named STATE.
def set_state(new_val):
  'Change the global state to a new value.'
  global STATE
  STATE = new_val

Global variables aren't the only time an outer scope is accessed. Outer scopes are accessed any time state is encapsulated. Take a look at how Python's methods work. Every variable and method must be accessed through the 'self' keyword or Python will assume the binding is local.

## The usage of self is mandatory.
class Cat(object):
  def __self__(self, color, age):
    self.color = color
    self.age = age
    self.meow()
  def meow(self):
    print 'Meow!'

Finally, the most general case is closures. Python 2 will never have a fix for this, and Python 3 fixes this by adding another keyword, 'nonlocal'. The 'nonlocal' keyword is similar to the 'global' keyword, but it applies to local scopes instead of the module level scope.

def make_counter():
  n = 0
  def counter():
    nonlocal n
    n += 1
    return n
  return counter

counter = make_counter()
print(counter(), counter(), counter())

Food for Thought

There are more combinations of type systems and variable declarations than mentioned so far.

In the dynamicaly typed language Lua, global variables are not declared, but local variables must be. If a local variable is not declared, it is treated as global.

local foo = 3

Haskell is statically typed, but has type inference, so the declarations are used to introduce new bindings and always assign a value.

let x = 3 in x + x

End the Madness

Now that you know that scope and type are unrelated, expect better static analysis from dynamically typed languages and create better languages. It's hard enough to be productive without weird conventions getting in the way.

Trouble with you is the trouble with me.

Got two scopes but our variables are free.

-Anonymous3

Footnotes:

Footnotes:

1

Original Archive

2

With the 'strict' pragma activated.

3

This was probably based on the The Grateful Dead song "Casey Jones"