A Mental Model of JavaScript
Part 2: Functions
Summary from last class
Key principles of variables, objects, and memory:
Important corollary: Objects are not values. Arrays are not values.
This principles are true for JavaScript and every other major programming language (with the exception of C++, where objects can be values).
Revision: Changing Variable Types
let a = [2, 3, 4];
// Do something: 1
a = 2;
// Do something: 2
a = { x: 2 };
// Do something: 3
a = function() {
console.log("Hello World");
}
// Do something: 4
Revision: Changing Variable Types
let a = [2, 3, 4];
// Do something: 1
a = 2;
// Do something: 2
a = { x: 2 };
// Do something: 3
a = function() {
console.log("Hello World");
}
// Do something: 4
Q: Where can these statements go?
a.x = 3;
a();
console.log(2 * a);
console.log(a.length);
Revision: How many objects will there be?
let a = [];
let b = { x: 0, y: 1 };
for (let i = 0; i < 100; ++i) {
a.push(b);
}
let a = [];
let b = { x: 0, y: 1 };
for (let i = 0; i < 100; ++i) {
b = { x: 0, y: 1 };
a.push(b);
}
Revision: Values, and Memory Map
let a = [];
let b = { x: 0, y: 1 };
for (let i = 0; i < 100; ++i) {
b = { x: 0, y: 1 };
a.push(b);
}
b.x = 5;
let s = a.reduce(function(sum, obj) { return sum + obj.x;}, 0);
console.log(s);
Mechanism of function calls
function f1(a, y) {
let b = a;
b.x = b.x + y;
return b;
}
let a = { x: 0 };
let y = 10;
let b = f1(a, y);
What are the values of the variables, and the memory layout before, during, and after the function call?
Exercise: What will this program print, and why?
let a = { x: 0, y: 1 };
function doSomething(z) {
z.x = 42;
}
doSomething(a);
console.log(a);
Exercise: What will this program print, and why?
let a = { x: 0, y: 1 };
function doSomething(z) {
z = { x: 42, y: 1};
}
doSomething(a);
console.log(a);
Exercise: What will this program print, and why?
let a = { x: 0, y: 1 };
function doSomething(z) {
z.x = 42;
return z;
}
let b = doSomething(a);
b.x = 100;
console.log(a.x);
Exercise: What will this program print, and why?
let a = [];
let o = { x: 0 };
function f(a, o) {
for (let i = 0; i < 10; ++i) {
a.push(o);
}
o.x = 1;
return o;
}
let b = f(a, o);
b.x = 2;
console.log(a.reduce(function(a, b) { return a + b.x; }, 0));
Program Stacks
From 187
Bottom Frame
Top Frame
Frame [0]
return link to caller
Frame [1]
return link to [0]
Frame [N]
return link to [N-1]
… …
Available Stack Space
From 187
Stack example
Top Frame:
y = undefined
(end of program)
function G() Frame:
x = undefined
return to line 8
Top Frame:
y = undefined
(end of program)
function G() Frame:
x = undefined
return to line 8
function F() Frame:
return to line 5
Top Frame:
y = undefined
(end of program)
Top Frame:
y = undefined
(end of program)
function G() Frame:
x = 10
return to line 8
Stack example (contd.)
Top Frame:
y = undefined
(end of program)
function G() Frame:
x = 10
return to line 8
Top Frame:
y = 12
(end of program)
Top Frame:
y = 12
(end of program)
Top Frame:
y = 12
(end of program)
What is the value of z?
Answer: 12
But, does the model of the stack work?
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
What is the value of x?
What went wrong?
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
When we return g, we pop the stack frame that holds the value of x. Therefore when we call this function (with the name h), we get into trouble.
Solution: closures
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
g contains an object with two fields: (1) a dictionary that maps variables to their values and (2) the code of g.
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h = F(10);
let z = h(2);
When we call a closure, we first restore the values saved in the closure object and then enter the function in the closure object.
Notice that both x and y are on the stack, so we will no problem calculating x + y.
Main principles
Are functions values? Not exactly. It is more precise to say that closures are values that contain functions
Another example
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
function app(f, z) {
return f(z);
}
let h = F(10);
let w = app(h, 20);
Compare this stack with the earlier example, when we called h(1) directly.
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h1 = F(10);
let h2 = F(200);
let z = h1(1) + h2(2000);
Work out all the steps of execution. Notice that the closures h1 and h2 both contain the same code, but they have different values for x.
function F(x) {
let g = function(y) {
return x + y;
}
return g;
}
let h1 = F(3);
let h2 = F(h1(5));
let z = h2(10);
What is the value of x in the closure h2?
function F(x) {
let g = function(y) {
let r = x + y;
if (y === 0) {
x = 0;
}
return r;
}
return g;
}
let h = F(10);
let z = h1(0);
let w = h1(0);
What are the values of z and w?
function M0(f, x) {
return x;
}
console.log(M0(F, X));
Can you come up with values of F and X, such that this program prints 0?
function M0(f, x) {
return x;
}
function M1(f, x) {
return f(x);
}
console.log(M0(F, X));
console.log(M1(F, X));
Can you come up with values of F and X, such that this program prints 0 and 1?
Can you come up with values of F and X, such that this program prints 0, 1, and 3?
function M0(f, x) {
return x;
}
function M1(f, x) {
return f(x);
}
function M3(f, x) {
return f(f(f(x)));
}
console.log(M0(F, X));
console.log(M1(F, X));
console.log(M3(F, X));
Can you come up with values of F and X, such that this program prints 0, 1, and 3?
Answer is:
X = 0
F = function(y) { return y + 1; }
Other crazier answers are also possible, but this is the simplest.
function M0(f, x) {
return x;
}
function M1(f, x) {
return f(x);
}
function M3(f, x) {
return f(f(f(x)));
}
console.log(M0(F, X));
console.log(M1(F, X));
console.log(M3(F, X));
Exercise: What will this program print, and why?
let a = { x: 0, y: 1 };
function doSomething(z) {
z = { x: 42, y: 1};
return z;
}
let b = doSomething(a);
b.x = 100;
console.log(a.x);
Miscellaneous Points
Objects and Closures
function M(x) {
let f1 = function() {
return x;
}
let f2 = function(y) {
x = y;
}
return {
get: f1,
set: f2
};
}
let obj = M(100);
Run these in the terminal:
How does this work?
function M(x) {
let f = function(y) {
let g = function() {
let r = x + y;
if (y === 0) {
x = 0;
}
return r;
}
return g;
}
return f;
}
let h = M(100);
console.log(h(0)());
console.log(h(0)());
According to our mental model of closures, what should this program display?
What does it actually display?
function M(x) {
let f = function(y) {
let g = function() {
let r = x.v + y;
if (y === 0) {
x.v = 0;
}
return r;
}
return g;
}
return f;
}
let h = M({ v: 100 });
console.log(h(0)());
console.log(h(0)());
According to our mental model of closures, what should this program display? (Work it out at home!)
What does it actually display?
Can we characterize when our mental model breaks down?
Answer: When a function assigns to a variable in its closure. x.v = 0 is not an assignment.
Thought experiment: come up with a mental model that actually works