Skip to content

Instantly share code, notes, and snippets.

@gatlin
Last active August 29, 2015 14:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gatlin/ae4e52f4c6804bb15d31 to your computer and use it in GitHub Desktop.
Save gatlin/ae4e52f4c6804bb15d31 to your computer and use it in GitHub Desktop.
/**
* Run with node.js:
*
* $> node skiing.js
*
* For a decent overview of what S and K and the rest of all this is:
*
* https://en.wikipedia.org/wiki/SKI_combinator_calculus
*
* This is one of those things where the actual theory isn't difficult, but
* that means using it *is*. :)
*/
// S and K combinators. Total magic. wow
var S = function (x) { return (function (y) { return (function (z) {
return (x(z))(y(z)); }); }); };
var K = function (x) { return (function (y) { return x; }); };
// I combinator, which is an identity function ( I(5) === 5 )
var I = (S(K))(K);
// Composition of two functions
var compose = (S(K(S)))(K);
// Using these 4 functions we can define a monad[1] over unary functions.
// What does this monad do, though? Take these definitions for granted for now:
var ret = K;
var join = S(((S(K(S(I))))(K)));
var flatMap = function (ma, f) {
return join((compose(f))(ma)); };
// This isn't strictly necessary but the code will make more sense when you
// read it:
var ask = I;
// The inner function will "ask" for some magic value and return whether or not
// it is an even number:
var r1 = flatMap(ask, function(n) {
// ^-- how does this get us a value?
var itIsEven = (n % 2 === 0);
return ret(itIsEven); });
// And this function will take a boolean value indicating whether the magic
// number was even, and then compose a sentence using it and the magic number:
var r2 = function(itIsEven) {
return flatMap(ask, function(n) {
// ^-- there it is again!
var s = n.toString();
return ret (s + (itIsEven ? " is even!" : " is odd!"));
});
};
// `flatMap` lets us stitch these two functions together:
var r3 = flatMap(r1, r2);
console.log(r1(10)); // 10 will be the magic number; returns true
console.log(r3(10)); // 10 will again be the magic number;
// returns the string "10 is even!"
/*
* So with those definitions I can write functions which have both arguments
* and a magic embedded value, and when I stitch them together they'll have the
* *same* magic value.
*/
// convenience
function plus1 (x) { return x + 1; }
// `local` may be used to modify the magic value locally for a sub-computation;
// though it will be reverted when the sub-computation terminates.
var local = function(f,m) { return (compose(m))(f); };
// This function locally modifies its magic value and then calls r1
var r4 = flatMap(ask, function(n) {
return local(plus1, r1); });
// Analogous to r3
var r5 = flatMap(r4, r2);
console.log(r5(10)); // returns the (stupid) string "10 is odd!"
/*
[^1] Monad: In part, a thing which you can map a function over, like an
array. Eg:
[1,2,3].map(function(x) { return x * x; }) ==> [1,4,9]
However, a monad also lets you change the structure of the container, not just
the values, while mapping:
[1,2,3].flatMap(function(x) { return [x,x]; }) ==> [1,1,2,2,3,3]
In that case, I made the array twice as long. Or I could do this:
[1,2,3].flatMap(function(x) { return (x % 2 === 0) ? [] : [x]; }) ==> [1,3]
In that case I made the array shorter. The choice of the name `flatMap` comes
from the fact that instead of returning the new mapped value, you return a
whole new structure for each element, and these are "flattened" out into one.
I defined a whole bunch of monads (and other structures) here, and I'm
thinking of making it into a library:
https://gist.github.com/gatlin/42e32522f9c6ab808af3
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment