Last active
April 17, 2017 05:46
-
-
Save jurStv/787d4b2eb48b9e696793 to your computer and use it in GitHub Desktop.
From Functional Programming in Javascript book
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Here are five different implementations of the same floorSqrt() | |
functional composition. | |
They seem to be identical, but they deserve scrutiny. | |
But there are a few key differences we should go over: | |
Obviously the first method is verbose and inefficient. | |
The second method is a nice one-liner, but this approach becomes very unreadable | |
after only a few functions are applied. | |
The third approach is a chain of array functions, notably the map function. This works | |
fairly well, but it is not mathematically correct. | |
Here’s our compose() function in action. All methods are forced to be unary, pure | |
functions that encourage the use of better, simpler, and smaller functions that do one | |
thing and do it well. | |
The last approach uses the compose() function in reverse sequence, which is just as | |
valid. | |
*/ | |
function floorSqrt1(num) { | |
var sqrtNum = Math.sqrt(num); | |
var floorSqrt = Math.floor(sqrtNum); | |
var stringNum = String(floorSqrt); | |
return stringNum; | |
} | |
function floorSqrt2(num) { | |
return String(Math.floor(Math.sqrt(num))); | |
} | |
function floorSqrt3(num) { | |
return [num].map(Math.sqrt).map(Math.floor).toString(); | |
} | |
var floorSqrt4 = String.compose(Math.floor).compose(Math.sqrt); | |
var floorSqrt5 = Math.sqrt.sequence(Math.floor).sequence(String); | |
// all functions can be called like this: | |
floorSqrt/*<N>*/(17); // Returns: 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var fcompose = function() { | |
// first make sure all arguments are functions | |
var funcs = arrayOf(func)(arguments); | |
// return a function that applies all the functions | |
return function() { | |
var argsOfFuncs = arguments; | |
for (var i = funcs.length; i > 0; i -= 1) { | |
argsOfFuncs = [funcs[i].apply(this, args)]; | |
} | |
return args[0]; | |
}; | |
}; | |
// example: | |
var f = fcompose(negate, square, mult2, add1); | |
f(2); // Returns: -36 | |
var curry = function(f) { | |
var recur; | |
var __slice = [].slice; | |
return (recur = function(as) { | |
var next; | |
next = function() { | |
var args, bs; | |
bs = 1 <= arguments.length ? __slice.call(arguments, 0) : []; | |
args = (as || []).slice(0); | |
if (args.push.apply(args, bs) < f.length && bs.length) { | |
return recur(args); | |
} | |
return f.apply(null, args); | |
}; | |
if (f.length > 1) { | |
return next; | |
} else { | |
return f; | |
} | |
})(); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
While morphisms are mappings between types, functors are mappings | |
between categories. They can be thought of as functions that lift values out | |
of a container, morph them, and then put them into a new container. | |
The first input is a morphism for the type and the second input is the container. | |
The type signature for functors looks like this: | |
myFunctor :: (a -> b) -> f a -> f b | |
It turns out we already have one functor: map(). | |
It grabs the values within the container, an array, and applies a function to it. | |
*/ | |
// map :: (a -> b) -> [a] -> [b] | |
var map = function(f, a) { | |
return arr(a).map(func(f)); | |
} | |
var strmap = function(f, s) { | |
return str(s).split('').map(func(f)).join(''); | |
} | |
// arrayOf :: (a -> b) -> ([a] -> [b]) | |
var arrayOf = function(f) { | |
return function(a) { | |
return map(func(f), arr(a)); | |
} | |
} | |
var plusplusall = arrayOf(plusplus); // plusplus is our morphism | |
console.log( plusplusall([1,2,3]) ); // returns [2,3,4] | |
console.log( plusplusall([1,'2',3]) ); // error is thrown | |
var strs = arrayOf(str); | |
console.log( strs(['a','b','c']) ); // returns ['a','b','c'] | |
console.log( strs(['a',2,'c']) ); // throws error |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
First, we’ll need a function that helps us create morphisms. | |
We’ll call it homoMorph() because they’ll be homomorphisms. | |
It will return a function that expects a function to be | |
passed in and produces the composition of it, based on the inputs. | |
The inputs are the types that the morphism accepts as input and gives as output. | |
*/ | |
var homoMorph = function( /* input1, input2,..., inputN, output */ ) { | |
var before = checkTypes(arrayOf(func) | |
(Array.prototype.slice.call(arguments, 0, arguments.length-1))); | |
var after = func(arguments[arguments.length-1]) | |
return function(middle) { | |
return function(args) { | |
return after(middle.apply(this, before([].slice.apply(arguments)))); | |
} | |
} | |
} | |
// now we don't need to add type signature comments | |
// because now they're built right into the function declaration | |
add = homoMorph(num, num, num)(function(a,b){return a+b}) | |
add(12,24); // returns 36 | |
add('a', 'b'); // throws error | |
homoMorph(num, num, num)(function(a,b){ | |
return a+b; | |
})(18, 24); // returns 42 | |
/* | |
It uses a closure to return a function that accepts a function | |
and checks its input and output values for type safety. | |
*/ | |
var checkTypes = function( typeSafeties ) { | |
arrayOf(func)(arr(typeSafeties)); | |
var argLength = typeSafeties.length; | |
return function(args) { | |
arr(args); | |
if (args.length != argLength) { | |
throw new TypeError('Expected '+ argLength + ' arguments'); | |
} | |
var results = []; | |
for (var i=0; i<argLength; i++) { | |
results[i] = typeSafeties[i](args[i]); | |
} | |
return results; | |
} | |
} | |
//Now let’s formally define some homomorphisms. | |
var lensHM = homoMorph(func, func, func)(lens); | |
var userNameHM = lensHM( | |
function (u) {return u.getUsernameMaybe()}, // get | |
function (u, v) { // set | |
u.setUsername(v); | |
return u.getUsernameMaybe(); | |
} | |
); | |
var strToUpperCase = homoMorph(str, str)(function(s) { | |
return s.toUpperCase(); | |
}); | |
var morphFirstLetter = homoMorph(func, str, str)(function(f, s) { | |
return f(s[0]).concat(s.slice(1)); | |
}); | |
var capFirstLetter = homoMorph(str, str)(function(s) { | |
return morphFirstLetter(strToUpperCase, s) | |
}); | |
//The following example includes function composition, lenses, homomorphisms, and more | |
// homomorphic lenses | |
var bill = new User(); | |
userNameHM.set(bill, 'William'); // Returns: 'William' | |
userNameHM.get(bill); // Returns: 'William' | |
// compose | |
var capatolizedUsername = fcompose(capFirstLetter,userNameHM.get); | |
capatolizedUsername(bill, 'bill'); // Returns: 'Bill' | |
// it's a good idea to use homoMorph on .set and .get too | |
var getUserName = homoMorph(obj, str)(userNameHM.get); | |
var setUserName = homoMorph(obj, str, str)(userNameHM.set); | |
getUserName(bill); // Returns: 'Bill' | |
setUserName(bill, 'Billy'); // Returns: 'Billy' | |
// now we can rewrite capatolizeUsername with the new setter | |
capatolizedUsername = fcompose(capFirstLetter, setUserName); | |
capatolizedUsername(bill, 'will'); // Returns: 'Will' | |
getUserName(bill); // Returns: 'will' | |
/* | |
What does it mean for code to be declarative? In imperative programming, we write | |
sequences of instructions that tell the machine how to do what we want. | |
In functional programming, we describe relationships between values | |
that tell the machine what we want it to compute, and the machine figures out | |
the instruction sequences to make it happen. | |
Functional programming is declarative. | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Monads are tools that help you compose functions. | |
Like primitive types, monads are structures that can be used as the containers | |
that functors “reach into”. The functors grab the data, do something to it, | |
put it into a new monad, and return it. | |
There are three monads we’ll focus on: | |
Maybes | |
Promises | |
Lenses | |
... | |
jQuery, the popular JavaScript library that provides an | |
enhanced interface for working with HTML is, in-fact, a monadic library. | |
The jQuery object is a monad and its methods are functors. | |
Really, they’re a special type of functor called endofunctors. | |
Endofunctors are functors that return the same category as the input, that is, | |
F :: X -> X. Each jQuery method takes a jQuery object and returns a | |
jQuery object, which allows methods to be chained, | |
and they will have the type signature | |
jFunc :: jquery-obj -> jquery-obj. | |
Monads are the containers that the functors “reach into” to get the data. | |
In this way, the data can be protected and controlled by the library. | |
jQuery provides access to the underlying data, a wrapped set of HTML elements, | |
via its many methods. | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Lenses are first-class getters and setters. They allow us to not just get and set variables, | |
but also to run functions over it. But instead of mutating the data, they clone and return the | |
new data modified by the function. They force data to be immutable, which is great for | |
security and consistency as well for libraries. They’re great for elegant code no matter | |
what the application, so long as the performance-hit of introducing additional array copies | |
is not a critical issue. | |
Before we write the lens() function, let’s look at how it works. | |
*/ | |
var first = lens( | |
function (a) { return arr(a)[0]; }, // get | |
function (a, b) { return [b].concat(arr(a).slice(1)); } // set | |
); | |
first([1, 2, 3]); // outputs 1 | |
first.set([1, 2, 3], 5); // outputs [5, 2, 3] | |
function tenTimes(x) { return x * 10 } | |
first.modify(tenTimes, [1,2,3]); // outputs [10,2,3] | |
//And here’s how the lens() function works. | |
//It returns a function with get, set and mod defined. | |
//The lens() function itself is a functor. | |
var lens = function (get, set) { | |
var f = function (a) {return get(a)}; | |
f.get = function (a) {return get(a)}; | |
f.set = set; | |
f.mod = function (f, a) {return set(a, f(get(a)))}; | |
return f; | |
}; | |
// userName :: User -> str | |
var userName = lens( | |
function (u) {return u.getUsernameMaybe()}, // get | |
function (u, v) { // set | |
u.setUsername(v); | |
return u.getUsernameMaybe(); | |
} | |
); | |
var bob = new User(); | |
bob.setUsername('Bob'); | |
userName.get(bob); // returns 'Bob' | |
userName.set(bob, 'Bobby'); //return 'Bobby' | |
userName.get(bob); // returns 'Bobby' | |
userName.mod(strToUpper, bob); // returns 'BOBBY' | |
strToUpper.compose(userName.set)(bob, 'robert'); // returns 'ROBERT' | |
userName.get(bob); // returns 'robert' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Promises are like the functional equivalent of callbacks. Obviously, callbacks are not all | |
that functional because, if more than one function is mutating the same data, then there | |
can be race conditions and bugs. Promises solve that problem. | |
*/ | |
// the Promise monad | |
var Promise = require('bluebird'); | |
// the promise functor | |
var promise = function(fn, receiver) { | |
return function() { | |
var slice = Array.prototype.slice, | |
args = slice.call(arguments, 0, fn.length - 1), | |
promise = new Promise(); | |
args.push(function() { | |
var results = slice.call(arguments), | |
error = results.shift(); | |
if (error) promise.reject(error); | |
else promise.resolve.apply(promise, results); | |
}); | |
fn.apply(receiver, args); | |
return promise; | |
}; | |
}; | |
//Now we can use the promise() functor to transform functions | |
//that take callbacks into functions that return promises. | |
var files = ['a.json', 'b.json', 'c.json']; | |
readFileAsync = promise(fs.readFile); | |
var data = files | |
.map(function(f){ | |
readFileAsync(f).then(JSON.parse) | |
}) | |
.reduce(function(a,b){ | |
return $.extend({}, a, b) | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var trampoline = function(f) { | |
while (f && f instanceof Function) { | |
f = f.apply(f.context, f.args); | |
} | |
return f; | |
}; | |
var thunk = function (fn) { | |
return function() { | |
var args = Array.prototype.slice.apply(arguments); | |
return function() { return fn.apply(this, args); }; | |
}; | |
}; | |
var factorial = function(n) { | |
var _fact = thunk(function(x, n) { | |
if (n == 0) { | |
// base case | |
return x; | |
} | |
else { | |
// recursive case | |
return _fact(n * x, n - 1); | |
} | |
}); | |
return trampoline(_fact(1, n)); | |
}; | |
var treeTraverse = function(trunk) { | |
var _traverse = thunk(function(node) { | |
node.doSomething(); | |
node.children.forEach(_traverse); | |
}); | |
trampoline(_traverse(trunk)); | |
}; | |
//Y-COMBINATOR | |
var Y = function(F) { | |
return (function (f) { | |
return f(f); | |
} (function (f) { | |
return F(function (x) { | |
return f(f)(x); | |
}); | |
})); | |
}; | |
var Ymem = function(F, cache) { | |
if (!cache) { | |
cache = {} ; // Create a new cache. | |
} | |
return function(arg) { | |
if (cache[arg]) { | |
// Answer in cache | |
return cache[arg] ; | |
} | |
// else compute the answer | |
var answer = (F(function(n){ | |
return (Ymem(F,cache))(n); | |
}))(arg); // Compute the answer. | |
cache[arg] = answer; // Cache the answer. | |
return answer; | |
}; | |
} | |
var FactorialGen = function (factorial) { | |
return function(n) { | |
var factorial = thunk(function (x, n) { | |
if (n == 0) { | |
return x; | |
} | |
else { | |
return factorial(n * x, n - 1); | |
} | |
}); | |
return trampoline(factorial(1, n)); | |
} | |
}; | |
var Factorial = Ymem(FactorialGen); | |
Factorial(10); // 3628800 | |
Factorial(23456); // Infinity | |
/* | |
the most efficient and safest method of performing recursion in | |
JavaScript is to use the memoizing Y-combinator with tail-call elimination | |
via trampolining and thunks. | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var typeOf = function(type) { | |
return function(x) { | |
if (typeof x === type) { | |
return x; | |
} | |
else { | |
throw new TypeError("Error: "+type+" expected, "+typeof x+" given."); | |
} | |
} | |
} | |
var objectTypeOf = function(name) { | |
return function(o) { | |
if (Object.prototype.toString.call(o) === "[object "+name+"]") { | |
return o; | |
} | |
else { | |
throw new TypeError("Error: '+name+' expected, something else given."); | |
} | |
} | |
} | |
var str = typeOf('string'), | |
num = typeOf('number'), | |
func = typeOf('function'), | |
bool = typeOf('boolean'), | |
obj = objectTypeOf('Object'), | |
arr = objectTypeOf('Array'), | |
date = objectTypeOf('Date'), | |
div = objectTypeOf('HTMLDivElement'); | |
// unprotected method: | |
var x = '24'; | |
x + 1; // will return '241', not 25 | |
// protected method | |
// plusplus :: Int -> Int | |
function plusplus(n) { | |
return num(n) + 1; | |
} | |
plusplus(x); // throws error, preferred over unexpected output |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment