1 of 40

A Mental Model of JavaScript

Part 2: Functions

2 of 40

Summary from last class

Key principles of variables, objects, and memory:

  1. The values are numbers, booleans, strings, and references to objects and arrays
  2. Every variable stores a value
  3. Every field in an object stores a value
  4. An assignment, y = x creates a copy of the value in x and stores that copy in y

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).

3 of 40

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

4 of 40

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);

5 of 40

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);

}

6 of 40

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);

7 of 40

Mechanism of function calls

  1. Parameters are passed in by copying values
  2. Return values are returned by copying values
  3. Beware: Objects are not values! References to objects are values.

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?

8 of 40

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);

9 of 40

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);

10 of 40

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);

11 of 40

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));

12 of 40

Program Stacks

  • What’s really going on with recursion? How does the computer system handle recursive calls?
  • Computers use stacks to handle all method calls and returns. Method calls are executed in Last-In-First-Out (LIFO) fashion (recall the ‘clean your house’ example and all the additional tasks it incurred).
  • A method’s local variables and return link are kept in the stack memory. This is called a stack frame.
  • The run-time system has a limited amount of stack memory. Beyond that you will get StackOverflowException.

From 187

13 of 40

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

14 of 40

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)

  1. function F() {
  2. return 10;
  3. }
  4. function G() {
  5. let x = F();
  6. return x + 2;
  7. }
  8. let y = G();
  9. console.log(y);
  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);
  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);

Top Frame:

y = undefined

(end of program)

function G() Frame:

x = 10

return to line 8

  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);

15 of 40

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)

  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);
  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);
  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);
  • function F() {
  • return 10;
  • }
  • function G() {
  • let x = F();
  • return x + 2;
  • }
  • let y = G();
  • console.log(y);

Top Frame:

y = 12

(end of program)

16 of 40

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);

17 of 40

function F(x) {

let g = function(y) {

return x + y;

}

return g;

}

let h = F(10);

let z = h(2);

18 of 40

  1. We push the return address (i.e., the address of the call-site of F) onto the stack
  2. We push the value of the argument x onto the stack

function F(x) {

let g = function(y) {

return x + y;

}

return g;

}

let h = F(10);

let z = h(2);

19 of 40

function F(x) {

let g = function(y) {

return x + y;

}

return g;

}

let h = F(10);

let z = h(2);

20 of 40

function F(x) {

let g = function(y) {

return x + y;

}

return g;

}

let h = F(10);

let z = h(2);

21 of 40

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?

22 of 40

What went wrong?

  • In 121 / 187, you learned that local variables are stored on the stack. This is wrong for most programming languages that are not Java or C++.
  • We said that functions are values. This is not completely accurate either.

23 of 40

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.

24 of 40

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.

25 of 40

function F(x) {

let g = function(y) {

return x + y;

}

return g;

}

let h = F(10);

let z = h(2);

26 of 40

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.

27 of 40

Main principles

  1. The values of JavaScript are numbers, booleans, strings, references to objects, references to arrays, and closures
  2. Every variable stores a value
  3. An assignment x = y creates a copy of the value in y and stores it in x
  4. Every field in an object stores a value
  5. A closure in an object with two fields: (1) the code of a function and (2) a dictionary that contains the values of variables that were in scope when the function was defined.

Are functions values? Not exactly. It is more precise to say that closures are values that contain functions

28 of 40

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.

29 of 40

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.

30 of 40

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?

31 of 40

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?

32 of 40

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?

33 of 40

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?

34 of 40

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));

35 of 40

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));

36 of 40

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);

37 of 40

Miscellaneous Points

  1. Do you have to always think in closures? No! You need to think of closures when a function (1) is used as an argument to a higher-order function or (2) is nested within another function and uses variables declared in the enclosing function. So, the simpler mental model from 121/187 is still valuable.
  2. Are closures expensive? No. For example, an implementation of JavaScript effectively use the simpler mental model when it is safe to do so and not create closures. Even when closures are created, they are not any slower than allocating an object.
  3. Are closures just objects? Yes. Are objects just closures? Yes. Are they they same? Not exactly. A closure stores exactly one function (“the” function). An object may have multiple functions (i.e., methods).
  4. Have we told you everything you need to know about closures? No.

38 of 40

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:

  1. obj.get()
  2. obj.set(20)
  3. obj
  4. obj.x
  5. let obj2 = M(10);
  6. obj.get / obj.set / obj2.get / obj2.set

How does this work?

39 of 40

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?

40 of 40

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