The Good Parts of JavaScript and the Web

Function the Ultimate

this

  • The this parameter contains a reference to the object of invocation.
  • this allows a method to know what object it is concerned with.
  • this allows a single function object to service many objects.
  • this is key to prototypal inheritance.

4 ways to call a function

  • Function form
    • functionObject(arguments)
  • Method from
    • thisObject.methodName(arguments)
    • thisObject[methodName](arguments)
  • Constructor form
    • new FunctionObject(arguments)
  • Apply form
    • functionObject.apply(thisObject, [arguments])

Function form

When a function is called in the function form, this is set to the global object.

Method from

When a function is called in the method form, this is set to thisObject, the object containing the function.

This allows methods to have a reference to the object of interest.

Constructor form

When a function is called with the new operator, a new object is created and assigned to this.

If there is not an explicit return value, then this will be returned.

Used in the Pseudoclassical style.

Apply form

A function’s apply or call method allows for calling the function, explicitly specifying thisObject.

It can also take an array of parameters or a sequence of parameters.

Closure

1
2
3
4
5
6
7
8
var digit_name = (function () {
var name = ['zero', 'one', 'two', 'three']
return function (n) {
return names[n]
}
})()

alert(digit_name(3)) // 'three

Side Effects

Programming without side-effects:
Remove assignment, loops(use recursion instead), freeze all array literals and object literals.

Remove Date and Math.random

Problems

funky

1
2
3
4
5
6
function funky (o) {
o = null
}
var x = []
funky(x)
alert(x)
What is x?
[]

swap

1
2
3
4
5
6
7
8
function swap (a, b) {
var temp = a
a = b
b = temp
}
var x = 1, y = 2;
swap(x, y)
alert(x)
What is x?
1

identity

Write a function that takes an argument returns that argument.

Solution

1
2
3
function identity (x) {
return x
}


Write two binary functions add and mul, that take two numbers and return their sum and product.

1
2
add(3, 4) // 7
mul(3, 4) // 12
Solution

1
2
3
4
5
6
7
function add (x, y) {
return x + y
}

function mul (x, y) {
return x * y
}


Write a function that takes an argument and return a function that return that argument

1
2
idf = identityf(3)
idf() // 3
Solution

1
2
3
4
5
function identityf(x) {
return function () {
return x
}
}


Write a function that adds from two invocations

1
addf(3)(4) // 7
Solution

1
2
3
4
5
function addf (x) {
return function (y) {
return x + y
}
}


Write a function that takes a binary function, and makes it callable with two invocations.

1
2
3
addf = applyf(add)
addf(3)(4) // 7
applyf(mul)(5)(6) // 30
Solution

1
2
3
4
5
6
7
function applyf (binary) {
return function (x) {
return function (y) {
return binary(x, y)
}
}
}


Write a function that takes a function and an argument, and returns a function that can supply a second argument.

1
2
3
4
add3 = curry(add, 3)
add3(4) // 7

curry(mul, 5)(6) // 30
Solution

1
2
3
4
5
function curry (func, first) {
return function (second) {
return func(first, second)
}
}


Without writing any new functions, show three ways to create the inc function

1
2
inc(5) // 6
inc(inc(5)) // 7
Solution

1
2
3
1. inc = addf(1)
2. inc = applyf(add)(1)
3. inc = curry(add, 1)


Write methodize, a function that converts a binary function to a method.

1
2
Number.prototype.add = methodize(add)
(3).add(4) // 7
Solution

1
2
3
4
5
6
7
8
9
10
11
function methodize (func) {
return function (y) {
return func(this, y)
}
}

function methodize (func) {
return function (...y) {
return func(this, ...y)
}
}


✔️ Write demethodize, a function that convert a method to a binary function.

1
demethodize(Number.prototype.add)(5, 6) // 11
Solution

1
2
3
4
5
6
7
8
9
10
11
function demethodize (func) {
return function (that, y) {
return func.call(that, y)
}
}

function demethodize (func) {
return function (that, ...y) {
return func.apply(that, y)
}
}


✔️ Write a function twice that takes a binary function and returns a unary function that passes its argument to the binary function twice.

1
2
3
4
var double = twice(add)
double(11) // 22
var square = twice(mul)
square(11) // 121
Solution

1
2
3
4
5
function twice (binary) {
return function (a) {
return binary(a, a)
}
}


✔️ Write a function composeu that takes two unary functions and returns a unary function that calls them both.

1
composeu(double, square)(3) // 36
Solution

1
2
3
4
5
function composeu (f, g) {
return function (a) {
return g(f(a))
}
}


✔️ Write a function composeb that takes two binary functions and returns a function that calls them both.

1
composeb(add, mul)(2, 3, 5) // 25
Solution

1
2
3
4
5
function composeb (f, g) {
return function (a, b, c) {
return g(f(a, b), c)
}
}


✔️ Write a function that allows another function to only be called once.

1
2
3
add_once = once(add)
add_once(3, 4) // 7
add_once(3, 4) // throw!
Solution

1
2
3
4
5
6
7
8
9
10
function once (func) {
return function () {
var f = func
func = null
return f.apply(
this,
arguments
)
}
}


✔️ Write a factory function that returns two functions that implement an up/down counter.

1
2
3
counter = counterf(10)
counter.inc() // 11
counter.dec() // 11
Solution

1
2
3
4
5
6
7
8
9
10
11
12
function counterf (value) {
return {
inc () {
value += 1
return value
},
dec () {
value -= 1
return value
}
}
}


✔️ Make a revocable function that takes a nice function, and returns a revoke function that denies access to the nice function, and an invoke function that can invoke the nice function until it is revoked.

1
2
3
4
temp = revocable(alert)
temp.invoke(7) // alert: 7
temp.revoke()
temp.invoke(8) // throw!
Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
function revocable (nice) {
return {
invoke () {
return nice.apply(
this,
arguments
)
},
revoke () {
nice = null
}
}
}


Monads & Gonads

Axioms

1
2
3
4
5
6
7
8
9
10
// 1
unit(value).bind(f) ==== f(value)

// 2
monad.bind(unit) ==== monad

// 3
monad.bind(f).bind(g) ==== monad.bind(function (value) {
return f(value).bind(g)
})

The Identity Monad

1
2
3
4
5
6
7
8
9
function MONAD () {
return function unit (value) {
var monad = Object.create(null)
monad.bind = function (func) {
return func(value)
}
return monad
}
}

How you call it:

1
2
3
var unit = MONAD()
var monad = unit("Hello world.")
monad.bind(alert)

The Ajax Monad

This tow are equivalent.

1
2
3
4
5
// monad.bind(func)
monad.bind(func, [a, b, c])

// monad.method()
monad.method(a, b, c)

❓Ajax Monad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function MONAD () {
var prototype = Object.create(null)
function unit (value) {
var monad = Object.create(prototype)
monad.bind = function (func, args) {
return func(value, ...args)
}
return monad
}

unit.lift = function (name, func) {
prototype[name] = function (...args) {
return unit(this.bind(func, args))
}
return unit
}

return unit
}

related:
Object.create()

Use Ajax Monad

1
2
3
4
var ajax = MONAD().lift('alert', alert)
var monad = ajax("Hello world.")
monad.bind(alert)
monad.alert()

❓The Maybe Monad

NaN: If you divide 0 by 0, you got NaN, instead of throw.

Maybe Monad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function MONAD (modifier) {
var prototype = Object.create(null)
function unit (value) {
var monad = Object.create(prototype)
monad.bind = function (func, args) {
return func(value, ...args)
}
if (typeof modifier === 'function') {
modifier(monad, value)
}
return monad
}
return unit
}

Use Maybe Monad

1
2
3
4
5
6
7
8
9
10
11
var maybe = MONAD(function (monad, value) {
if (value === null || value === undefined) {
monad.is_null = true
monad.bind = function () {
return monad
}
}
})

var monad = maybe(null)
monad.bind(alert)

The Promise Monad

Make a vow

1
2
3
4
5
6
var my_vow = VOW.make()

.keep(value)
.break(reason)
.promise
.when(kept, broken)

Promise Monad

1
2
3
4
5
6
7
8
9
10
11
var VOW = (function () {

// function enqueue here...
// function enlighten...

return {
make: function make () {
...
}
}
}())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
make: function make () {
var breaker = [], fate,
keeper = [], status = 'pending'

// function herald here...

return {
break: function (reason) {
herald('broken', reason, breakers)
},
keep: function (value) {
herald('kept', value, keepers)
},
promise: {
...
}
}
}
1
2
3
4
5
6
7
8
9
10
function herald (state, value, queue) {
if (status !== 'pending') {
throw "overpromise"
}
fate = value
status = state
enlighten(queue, fate)
keepers.length = 0
breakers.length = 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
promise: {
is_promise: true,
when: function (kept, broken) {
var vow = make()
switch (status) {
case 'pending':
enqueue(keepers, kept, vow.keep, vow.break)
enqueue(breakers, broken, vow.break, vow.break)
break
case 'kept':
enqueue(keepers, kept, vow.keep, vow.break)
enlighten(keepers, fate)
break
case 'broken':
enqueue(breakers, broken, vow.break, vow.break)
enlighten(breakers, fate)
break
}
return vow.promise
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function enqueue(queue, func, resolver, breaker) {
queue[queue.length] = typeof func !== 'function'
? resolver
: function (value) {
try {
var result = func(value)
if (result && result.is_promise === true) {
result.when(resolver, breaker)
} else {
resolver(result)
}
} catch (e) {
breaker(e)
}
}
}
1
2
3
4
5
function enlighten (queue, fate) {
queue.forEach(function (func) {
setImmediate(func, fate)
})
}