Skip to content

Instantly share code, notes, and snippets.

@jurStv
Last active April 17, 2017 05:46
Show Gist options
  • Save jurStv/787d4b2eb48b9e696793 to your computer and use it in GitHub Desktop.
Save jurStv/787d4b2eb48b9e696793 to your computer and use it in GitHub Desktop.
From Functional Programming in Javascript book
/*
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
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;
}
})();
};
/*
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
/*
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.
*/
/*
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.
*/
/*
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'
/*
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)
});
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.
*/
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