Skip to content

Instantly share code, notes, and snippets.

@CrossEye
Forked from buzzdecafe/Functor.js
Last active December 23, 2015 07:58
Show Gist options
  • Save CrossEye/6603935 to your computer and use it in GitHub Desktop.
Save CrossEye/6603935 to your computer and use it in GitHub Desktop.
First Functor Fantasy Follow-up
var curry = function(fn) {
var arity = fn.length;
var f = function(args) {
return function () {
var newArgs = (args || []).concat([].slice.call(arguments, 0));
if (newArgs.length >= arity) {
return fn.apply(this, newArgs);
}
else {return f(newArgs);}
};
};
return f([]);
};
var compose = function(f, g) {
return function() {
return f.call(this, (g.apply(this, arguments)));
};
};
var Maybe = function(val) {
if (!(this instanceof Maybe)) {return new Maybe(val);}
this.val = val;
};
var Either = function(left, right) {
if (!(this instanceof Either)) {return new Either(left, right);}
this.left = left;
this.right = right;
};
(function(global) {
// TODO: Make a tree structure from type objects' optional `parentKey` properties. Do a depth-first search of
// this tree instead of the simple linear search of `Object.keys`.
var types = {};
global.Functor = function(config) {
types[config.key] = config;
};
global.fmap = curry(function(f, obj) {
var type = types[obj.constructor.name], idx = -1, keys = Object.keys(types), len = keys.length;
if (!type || !type.test(obj)) {type = false;}
while (!type && ++idx < len) {
type = types[keys[idx]].test(obj) ? types[keys[idx]] : false;
}
if (!type) {
throw new TypeError("fmap called on unregistered type: " + obj)
}
return type.fmap(f, obj);
});
}(this));
var makeTypeof = function(type) {
return function(obj) {
return type.prototype.isPrototypeOf(obj);
};
};
Functor({
key: 'Array',
test: function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
},
fmap: function(f, arr){
return arr.map(function(x){return f(x);});
}
});
Functor({
key: 'Function',
test: function(fn) {
return Object.prototype.toString.call(fn) === '[object Function]';
},
fmap: function(f, g) {
return compose(f, g);
}
});
Functor({
key: 'Maybe',
test: makeTypeof(Maybe),
fmap: function(f, maybe) {
return (maybe.val == null) ? maybe : Maybe(f(maybe.val));
}
});
Functor({
key: 'Either',
test: makeTypeof(Either),
fmap: function(f, either) {
return (either.right == null) ? Either(f(either.left), null) : Either(either.left, f(either.right));
}
});
var plus1 = function(n) {return n + 1;};
var times2 = function(n) {return n * 2;};
console.log(fmap(plus1, [2, 4, 6, 8])); //=> [3, 5, 7, 9]
console.log(fmap(plus1, Maybe(5))); //=> Maybe(6)
console.log(fmap(plus1, Maybe(null))); //=> Maybe(null)
console.log(fmap(plus1, times2)(3)); //=> 7 (= 3 * 2 + 1)
console.log(fmap(plus1, Either(10, 20))); //=> Either(10, 21)
console.log(fmap(plus1, Either(10, null))); //=> Either(11, null)
console.log(fmap(plus1, {val: 5})); //=> TypeError: fmap called on unregistered type: [object Object]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment