1. Composition, Birds &Composition, Birds &
RecursionRecursion
A scenic hike through the combinatorialA scenic hike through the combinatorial
forest.forest.
3. Function composition is all about functionsFunction composition is all about functions
and so is Javascript. This allows us to moveand so is Javascript. This allows us to move
toward Functional Programming in Javascript.toward Functional Programming in Javascript.
Functions are First Class - meaning, functions can be
treated as values.
Allowing Higher Order Functions, ie functions that take
functions as arguments and return new functions.
The ability to use anonymous functions and closures.
Other functional features like statelessness, side-effect free,
currying/partial application and recursion.
4. Some Basic FunctionsSome Basic Functions
Let's create some basic
functions that we can reuse.
Each function will:
1. Take a single parameter.
2. Perform a single task with
that parameter, and
3. return a new value
// Define using function expressions
// because functions ARE values!
var double = function(n) { return n * 2; },
square = function(n) { return n * n; },
inc = function(n) { return n + 1; };
5. Working with FunctionsWorking with Functions
Now, let's use these to
process a list of numbers in
an "imperative" fashion.
imperative means telling
the "machine" how to do
something, and as a result
what you want to happen.
// Define using function expressions
// because functions ARE values!
var double = function(n) { return n * 2; },
square = function(n) { return n * n; },
inc = function(n) { return n + 1; };
var list = [1,2,3,4,5],
results = [];
for (var i=0, l=list.length; i < l; i++) {
var n = inc(list[i]);
n = square(n);
n = double(n);
results.push(n);
}
console.log(results);
// => [8, 18, 32, 50, 72]
6. Working with FunctionsWorking with Functions
Now, let's use these to
process a list of numbers in
an "declarative" fashion.
declarative means telling
the "machine" what you
would like to happen, and let
the computer figure out how
to do it.
// Define using function expressions
// because functions ARE values!
var double = function(n) { return n * 2; },
square = function(n) { return n * n; },
inc = function(n) { return n + 1; };
var list = [1,2,3,4,5], results;
results = list.map(inc)
.map(square)
.map(double);
console.log(results);
// => [8, 18, 32, 50, 72]
7. Worth noting...Worth noting...
By writing programs using a
declarative abstraction
Usually, we end up
writing less code than in
imperative style.
We can reason about our
code at a higher level,
focusing more on "what",
rather than the "how"
We build complexity by
"composing" declarative
functions.
// Define using function expressions
// because functions ARE values!
var double = function(n) { return n * 2; },
square = function(n) { return n * n; },
inc = function(n) { return n + 1; };
var list = [1,2,3,4,5], results;
results = list.map(inc)
.map(square)
.map(double);
console.log(results);
// => [8, 18, 32, 50, 72]
8. Using composition instead...Using composition instead...
We can use composition
directly to build complexity,
using a higher-order
function.
compose is a curried
function (reduced to series of
functions that each take 1
argument)
Accepts two functions as
arguments and returns a
function.
compose can be composed.
works right -> left
// Define using function expressions
// because functions ARE values!
var double = function(n) { return n * 2; },
square = function(n) { return n * n; },
inc = function(n) { return n + 1; };
var compose = function(f) {
return function(g) {
return function(x) {
return f(g(x));
};
};
};
var list = [1,2,3,4,5], results;
results = list.map(
compose(double)(compose(square)(inc))
);
console.log(results);
// => [8, 18, 32, 50, 72]
9. We can even compose map()We can even compose map()
Using another higher-order
function, mapWith, we can
now abstract and reuse
operations on lists.
We now have a set of
reusable abstractions:
a library of higher-order
functions that let us build up
complexity.
a set of domain specific
functions that focus on one
task and are easy to reason
about and test.
// Define using function expressions
// because functions ARE values!
var double = function(n) { return n * 2; },
square = function(n) { return n * n; },
inc = function(n) { return n + 1; };
var compose = function(f) {
return function(g) {
return function(x) {
return f(g(x));
};
};
};
var mapWith = function(fn) {
return function(list) {
return list.map(fn);
};
};
var list = [1,2,3,4,5],
modNumbers = mapWith(
compose(double)(compose(square)(inc))
);
var results = modNumbers(list);
console.log(results);
// => [8, 18, 32, 50, 72]
11. A combinator is a higher-order function thatA combinator is a higher-order function that
uses only function application and/or otheruses only function application and/or other
combinators to define a result from itscombinators to define a result from its
arguments. Meaning...arguments. Meaning...
A combinator is a function with no free variables, ie, its
output is determined solely by its input.
Is pure, in that it produces no side-effects and doesn't
modify its input.
Is a higher-order function whose input is a function
and result is also a function.
12. What does this have to do with birds?What does this have to do with birds?
Raymond Smullyan wrote a classic book
entitled "To Mock a Mockingbird" dedicated
to describing these sets of functional
combinators using birds and their
calls/responses as a metaphor.
This was in honor of Haskell Curry, the
creator of Combinatory Logic/Lambda
Calculus who was an avid orinthologist as
well as mathematician and logician.
13. Let's look at some birds...Let's look at some birds...
Combinatory logic uses a very simple,
direct syntax for writing expressions:
In this combinator (a Finch), the calls are
applied from left -> right, just as in
Javascript. So abc is the same as (ab)c.
You could say that combinators take a
string of symbols (like x, y, z, w, and so
forth), then they introduce some erasing,
some duplication, some permutation, and
add some parentheses.
Fabc => cba
14. The idiot bird (The idiot bird (identityidentity))
This bird, sometimes called identity takes any call you make to it and
it responds back with the same call.
// Ix => x
var idiot(x) { return x; }
15. The MockingbirdThe Mockingbird
This bird is a duplicative combinator, in that it has two copies of its input
(which is rare). If you call some fn x to the mockingbird it will respond as if
the function had called to itself.
Sometimes called the U-combinator, passing the idiot to the mockingbird
calls the idiot with itself and returns, the idiot.
MI => II => I but,
MM => MM => MM => ... expands forever
// Mx = xx
function mockingbird(x) {
return x(x);
}
16. The Bluebird (compose)The Bluebird (compose)
Our previous friend compose is the bluebird combinator. The core
of the bluebirds functionality is that it defers the application of x in
favor of first applying y.
Composing allows us to chain functions together, passing the
output of one as the input of the next.
// Bxyz = (x∘y)z = x(yz)
function bluebird(x) {
return function(y) {
return function(z) {
return x(y(z));
};
};
}
17. The LarkThe Lark
When you call out some birds x and y to the lark, it will respond by
calling the sound of y out to the mockingbird, then taking the
response and calling it out to the bird x, or what is the same,
applying y to the mockingbird then applying that resulting bird to x.
The lark is a way to compose some function with self application.
// Lxy => x(My) => x(yy)
function lark(x) {
return function(y) {
return x(mockingbird(y));
};
}
// Or,... Lxy = BxMy = x(My) = x(yy)
function lark(x) {
return bluebird(x)(mockingbird);
}
18. The MeadowLarkThe MeadowLark
When you call out some x and y to the meadowlark, it will respond
by calling out y to the mockingbird of x.
Interestingly the meadowlark behaves much like the mockingbird
duplicating for all time. In fact, this bird differs from mockingbird only
in that it ensures that there is a y.
// Mₑxy = Mxy = xxy
function meadowlark(x) {
return function(y) {
return mockingbird(x)(y);
};
}
19. The Sage Bird (Y/Z Combinator)The Sage Bird (Y/Z Combinator)
// Sage Bird => Y-Combinator
function sage(f) {
return (function(x) {
return f(x(x));
})(function(x) {
return f(x(x));
});
}
The Sage Bird is more commonly known as the Y-Combinator,
created by Haskell Curry. If you look closely, you might notice some
of the previous combinator birds we've discussed so far.
The Y-Combinator is useful for performing recursion by finding the
fixed-point or convergence of a function (in recursive functions, the
convergence is where the recursion should stop; divergent functions run
forever).
// Lazy Y-Combinator (Z-Combinator)
function sage(f) {
return (function (x) {
return f(function (v) {
return x(x)(v);
});
})(function (x) {
return f(function (v) {
return x(x)(v);
});
}));
}
OR
20. The Sage Bird, How does it work?The Sage Bird, How does it work?
function factorial(recur) {
return function(n) {
return (n === 1) ? 1 : n * recur(n - 1);
};
};
sage(factorial)(1); // 1
sage(factorial)(12); // 479001600
In Javascript, the Y-Combinator isn't really needed to implement self
recursion, as we can easily define a name for our function and call it
ourself. Though, direct recursion and self/anonymous recursion still
have the problem of hitting a stack overflow.
However, the Y-Combinator is useful to understand and interesting
in its own right. Let's take a second and break it down using our new
found collection of birds.
21. function sage(f) {
return (function (x) {
return f(function (v) {
return x(x)(v);
});
})(function (x) {
return f(function (v) {
return x(x)(v);
});
});
}
function sage(f) {
return mockingbird(function (x) {
return f(function (v) {
return x(x)(v);
});
})
}
The Sage bird is composed of
various other birds. Let's work
from the outside-in.
At a high level, the Sage bird is
returning the invocation of a
function applied to itself. This
is the call of the Mockingbird,
so we can simplify.
// Mx = xx
function mockingbird(x) {
return x(x);
}
22. function sage(f) {
return mockingbird(function (x) {
return f(function (v) {
return x(x)(v);
});
})
}
Looking at the function
passed to Mockingbird, we
can see yet another
Mockingbird call x(x).
The larger function being
passed to our first
Mockingbird also looks
strikingly like the
MeadowLark, except the
function returned is
composed with f.
// Mₑxy = Mxy = xxy
function meadowlark(x) {
return function(y) {
return mockingbird(x)(y);
};
}
function sage(f) {
return mockingbird(function (x) {
return f(function (v) {
return mockingbird(x)(v);
});
})
}
23. function sage(f) {
return mockingbird(f(function (x) {
return function (v) {
return mockingbird(x)(v);
});
})
}
If we pull the composition with
f out of this function, it
becomes the Meadowlark, and
we can compose it with our
Bluebird.
// Mₑxy = Mxy = xxy
function meadowlark(x) {
return function(y) {
return mockingbird(x)(y);
};
}
function sage(f) {
return mockingbird(function (x) {
return f(function (v) {
return mockingbird(x)(v);
});
})
}
function sage(f) {
return mockingbird(bluebird(f)(meadowlark));
}
// Bxyz = (x∘y)z = x(yz)
function bluebird(x) {
return function(y) {
return function(z) {
return x(y(z));
};
};
}
24. function sage(f) {
return mockingbird(bluebird(f)(meadowlark));
}
// Lazy Y-Combinator (Z-Combinator)
function sage(f) {
return (function (x) {
return f(function (v) {
return x(x)(v);
});
})(function (x) {
return f(function (v) {
return x(x)(v);
});
}));
}
Understanding the Y-
combinator shows us:
1. The expressive power of
functional programming to
solve a unique problem
2. The problem: implementing
recursion when explicit
recursion isn't allowed
3. The power of combinators
and composition
25. // Test it out
function factorial(recur) {
return function(n) {
return n == 1 ? 1 : n * recur(n-1);
};
};
var fact = sage(factorial);
console.log(fact(10));
// => 3628800
console.log(fact(1000));
// => Infinity
console.log(fact(32768));
// => Uncaught RangeError: Maximum call stack size exceeded
The problem with recursion:The problem with recursion: Ye ole StackYe ole Stack
The problem with recursion in Javascript is that you
eventually run out of stack space because of the number
of function calls.
How do we solve this?
26. // Simple, recursive factorial function
function factorial(n) {
return n == 1 ? 1 : n * factorial(n-1);
}
The problem with recursion:The problem with recursion: Ye ole StackYe ole Stack
// Tail-call optimized factorial
function factorial(n, acc) {
acc = acc || 1;
return n ? factorial(n - 1, acc * n) : acc;
}
One way to solve this is to do tail-call optimization (done
at compile time), which means the function should return
either a value or a call to the function (without evaluating
its results)
Except, Javascript doesn't optimize for tail recursive calls
(Babel will, however).
27. Oh, well. I guess we'll just write loops...Oh, well. I guess we'll just write loops...
Trampolines to the rescue!
A trampoline is a mechanism for looping over thunk-
returning functions, and invoking them; simulating tail-
call elimination.
A thunk is just an un-evaluated expression,
so a "thunk-returning" function is one that returns aso a "thunk-returning" function is one that returns a
function that, when invoked, will perform the nextfunction that, when invoked, will perform the next
computationcomputation
28. Bouncy, bouncy, bouncy...Bouncy, bouncy, bouncy...
function factorial(n, acc) {
acc = acc || 1;
return n ?
function() { return factorial(n - 1, acc * n); } :
acc;
}
// trampoline decorator
function trampoline(fn){
return function(/*args*/){
var res = fn.apply(this, arguments);
while(res instanceof Function){
res = res();
}
return res;
}
}
trampoline(factorial)(32768)
// => Infinity
thunk
No need for
call stacks!
factorial returns a thunk if
there is another call to be
made; otherwise, it
returns a final value.
29. How does Babel do it?How does Babel do it?
// Tail-call optimized factorial
function factorial(n, acc) {
acc = acc || 1;
return n ?
factorial(n - 1, acc * n) :
acc;
}
Babel only supports tail-call
optimization on explicit function
calls.
It essentially turns the function
into iterative calls, eliminating
successive stack frames.
// Tail-call optimized factorial
"use strict";
function factorial(_x, _x2) {
var _again = true;
_function: while (_again) {
var n = _x,
acc = _x2;
_again = false;
acc = acc || 1;
if (n) {
_x = n - 1;
_x2 = acc * n;
_again = true;
continue _function;
} else {
return acc;
}
}
}