1) Bindings. 2) Open expressions, free variables, non-local references. 3) Closing an open expression. 4) Lexical scope. 5) Dynamic scope. 6) Higher order functions. a) Functions as parameters to functions. b) Functions as return values from functions. 7) Anonymous lambda expressions. 8) Nested lambda expressions (nested functions). 9) When a programming language has a) non-local references, b) lexical scope, c) nested functions, d) function parameters or function return values, then the language's interpreter (or compiler) must implement "closures". 10) Curried functions (an important example of nested anonymous lambda expressions) 1) Bindings. A binding is an association of a variable reference to a variable declaration. var x = 5; // this is a variable declaration var y = 1 + x; // this declares y, and x is bound to the above declaration 2) Open expressions, free variables, and non-local references. A free variable is a variable without a binding, that is a variable with no declaration associated to it. Free variables are also called "non-local references". Some examples of open expressions. Each expression has at least one free variable without any declaration (without any binding). m*x + b // three free variables 2*x^2 - 8 = 0 // one free variable x^2 + y^2 = 1 // two free variables f(x) = a*x^2 // one free variable g(x) = m*x + b // two free variables double f(double x) { return a * x * x; // one free variable } 3) Closing an open expression. A closed expression does not have any free variables. You close an open expression by providing a binding for each free variable. Examples of closing an open expression by providing a binding of each variable reference to a variable declaration. These examples come from math, logic, and programming languages. g(x,m,b) = m*x + b // m, x, b are bound to formal parameters let x = 2 in 2*x^2 - 8 = 0 // x is bound to the constant 2 int x = 2; 2*x*x - 8 == 0; // x is bound to the variable with value 2 let x = 1 in 2*x^2 - 8 = 0 // x is bound to the constant 1 { x | 2*x^2 - 8 = 0 } // x is bound to a local variable in the set definition There exits an x such that 2*x^2 - 8 = 0. // x is bound by a quantifier For all x, 2*x^2 - 8 = 0. // x is bound by a quantifier For all x, x^2 >= 0. // x is bound by a quantifier Let y = 5. For all x, x*y >= 0. // x is bound by a quantifier, y is bound to the constant 5 { (x,y) | x^2 + y^2 = 1 } // x, y are bound to local variables in the set definition let a = 10 in f(x) = a*x^2 // a is bound to the constant 10 double a = 10; int f(double x) { return a * x * x; // a is bound to the global variable } 4) Lexical scope. Lexical scope is a common way for a programming language to determine variable bindings. Variable bindings are determined by how code blocks are nested within each other. This is completely determined by how you write the code and does not depend on the order in which the code gets executed. Lexical scope is also called "static scope" because the binding of any variable reference can be determined "statically" (at "compile time") by just looking at the code. There is more than one kind of "lexical scope". For example, in the following code, the x in print(x) is bound to the first x in most lexically scoped languages, but in JavaScript, the x in print(x) is bound to the declaration in the next line. var x = 5; // this is a variable declaration { print(x); // which declaration of x is this x bound to? var x = 50; // this declares another variable called x } The languages C++, Java, Python, and JavaScript all use lexical scope, but they all use slightly different versions of lexical scope. 5) Dynamic scope. Dynamic scope is an obsolete way for a programming language to determine variable bindings. Variables references are bound to their most recently executed variable declaration. This is determined by the order in which the code gets executed. Here is an example that compares dynamic with lexical scope. Consider this C code. int y = 3; // declaration of a global y int f(int x) { return x + y; // y is a non-local reference } int g(int x) { int y = 1; // declaration of a local y return f(x); } f(0); // returns 3 (binds y reference in f to global y) g(0); // returns 3 with lexical scope (binds y reference in f to global y) // returns 4 with dynamic scope (binds y reference in f to local y in g) With lexical scope, here is what the environments look like when g(0) is called and then g calls f(x), [Global Env] [ y, 3 ] [ f, lambda] [ g, lambda] / \ / \ [Local Env, g] [Local Env, f] [ y, 1 ] [ x, 0 ] [ x, 0 ] When f looks up the value of y in its environment, it sees y = 3. With dynamic scope, here is what the environments look like when g(0) is called and then g calls f(x), [Global Env] [y, 3 ] [f, lambda ] [g, lambda ] /|\ | | [Local Env, g] [ y, 1 ] [ x, 0 ] /|\ | | [Local Env, f] [ x, 0 ] When this f looks up the value of y in its environment, it sees y = 1 (the first y in the chain of environments), not y = 3. With lexical scope, the environments form a tree rooted at the global environment. The structure of this tree is determined by how the code has been written (but which environment objects are in the tree at any given time is determined by the execution of the code). With dynamic scope, the environments form a chain that ends with the global environment. Which environment objects are in this chain, and their order in the chain, is determined by the order in which functions get called while the program executes. This order is not predetermined by the code, so which variable declaration a variable reference gets bound to can depend on the order in which functions are called. This means that you cannot look at the code and always determine which declaration a variable will be bound to. You must run the code to find out. This makes dynamic scope buggy and unreliable. 6) Higher order functions. a) Functions as parameters to functions. (downwards funarg problem) b) Functions as return values from functions. (upwards funarg problem) 7) Anonymous lambda expressions. 8) Nested lambda expressions (nested functions). 9) When a programming language has a) non-local references b) lexical scope c) nested functions d) function parameters or function return values then the programming environment must implement "closures". C and C++ have properties (a), (b), and (d), but C and C++ do not have (c). Java has (a) and (b). Java kind of has (c) (nested and inner classes). Java kind of has (d) (using functional interfaces). Java does not implement real closures because the variables being closed must be (effectively) final. Pascal has (a), (b), (c), and function parameters, but Pascal does not have function return values. Modula-2 and Oberon have (a), (b), (c) and (d) but don't allow nested function as return values. The original version of Lisp had (a), (c), and (d) but it did not have (b) (Lisp had dynamic scope then). All modern functional programming languages (Scheme, Haskell, ML, F#, Scala, Clojure, etc.) have all four properties and they implement closures. JavaScript has all four properties and so JavaScript implements closures, but JavaScript's version of lexical scope is a bit unusual. Python has all four properties and so Python implements closures, but Python's version of lexical scope is also a bit unusual. 10) Curried functions (an important example of nested anonymous functions) Partial application