Skip to content

Instantly share code, notes, and snippets.

@datchley
Last active August 29, 2015 14:27
Show Gist options
  • Save datchley/e2351f4ec1e90721bcc2 to your computer and use it in GitHub Desktop.
Save datchley/e2351f4ec1e90721bcc2 to your computer and use it in GitHub Desktop.
Simple Functional/Pure Programming Exercise for UI Meetup (Bullhorn)
//
// This is the actual implementation using our minimal functional library
//
var validate = {
'values': function(o) { return !isNull(o) && !isUndefined(o) && (isBoolean(o) || isNumber(o) || isString(o)); },
'arrays': function(o) { return !isNull(o) && !isUndefined(o) && isArray(o); }
}
var build = {
'values': function(prop, val) { return [prop,"=",qs(val.toString())].join(''); },
'arrays': function(prop, val) { return [prop,"=",val.toString()].join(''); }
};
// Returns an array of arrays, where each nested array contains the string values for each
// parameter to be passed in model, ie [ ["key=val", "key=val"], ["key=val"],..]
// each nested array is for the set of possible param types in model ('arrays', 'values', etc.)
function buildQueryParams(model, data) {
return values(mapObjectWith(function(key, type) {
return values(mapObjectWith(function(pname, val) {
if (!isNull(data[val]) && !isUndefined(data[val])) {
return build[key] && validate[key](data[val]) ? build[key](pname, data[val]) : undefined;
}
})(type));
})(model));
}
// example data
var model = {
values: {
'MyTitle': 'title',
'MySubTitle': 'subtitle'
},
arrays: {
'MyIds': 'ids'
}
};
var filterParams = {
ids: [1,2,3],
title: 'Hello World!'
}
// Using composition to build up a more complex sequence
var res = sequence(buildQueryParams.bind(this, model, filterParams), flatten, compact)().join(',');
// Test our results
console.log("Expecting [MyTitle='Hello World!',MyIds=1,2,3]: ", (res == "MyTitle='Hello World!',MyIds=1,2,3"));
console.log("> res =", res);
//
// A minimal functional style library
//
// Some type checkers...
['String','Number','Function','Date', 'Array'].forEach(function(type) {
var checkFn = 'is'+type;
window[checkFn] = function(o){ return Object.prototype.toString.call(o) == '[object ' + type + ']'; };
});
var isArray = Array['isArray'] || function(o) { return Object.prototype.toString.call(o) == '[object Array]'; };
function isBoolean(o) { return o === true || o === false || toString.call(o) === '[object Boolean]'; }
function isNull(o) { return o === null; }
function isUndefined(o) { return o === void 0; }
function isFalsey(o) {
return isNull(o) || isUndefined(o) || o === false;
}
// return a value as a quoted string (quote character optional)
function qs(val, ch) { ch = ch || "'"; return ch+val+ch; }
// return value of 'prop' in 'obj'
function get(obj, prop) { return obj[prop]; }
// return a copy of the array 'list' flattened by one level, ie [[1,2],[3,4]] = [1,2,3,4]
function flatten(list) {
return list.reduce(function(items, item) {
return isArray(item) ? items.concat(item) : item;
}, []);
}
// Returns just the list of values form the own properties of the
// given object `obj`
function values(obj) {
return Object.keys(obj).reduce(function(acc, key) {
acc.push(obj[key]);
return acc;
}, []);
}
// Return a modified version of function 'fn' that
// accepts its arguments in reverse
function flip(fn) {
return function() {
var args = [].slice.call(arguments);
return fn.apply(this, args.reverse());
};
}
// return a copy of the list with falsey values removed
function compact(list) {
return list.reduce(function(acc, item) {
if (!isFalsey(item)) {
acc.push(item);
}
return acc;
},[]);
}
// compose any number of functions, right to left, so
// ie, compose(f, g, h) = f(g(h()))
// output of h() -> g() -> f()
function compose() {
var args = [].slice.call(arguments),
f = args.shift(),
g = args.shift(),
fog = function() {
return f(g.apply(this, arguments));
},
len = args.length;
return len ? compose.apply(this, [fog].concat(args)) : fog;
}
// Reverse composition of compose so it reads left to right
// ie, sequence(f,g,h) = h(g(f()))
// output of f() -> g() -> h()
var sequence = flip(compose);
// return modified list that results from applying 'fn' to
// each item in 'list'
function map(list, fn) { return list.map(fn); }
// return the object created by mapping over the immdeidate
// keys of 'obj' and applying the function 'fn' to them
function mapObject(obj, fn) {
return Object.keys(obj).reduce(function(res, key) {
res[key] = fn.apply(this, [key, obj[key]]);
return res;
}, {});
}
// Right curried version of `map(list, fn)`
function mapWith(fn) {
return function(list) {
return map(list, fn);
};
}
// Return a flattend version of mapping `fn` over each
// item in `list`
function flatMapWith(fn) {
return function(list) {
return flatten(map(list, fn));
};
}
// Right curried version of `mapObject(list, fn)`
function mapObjectWith(fn) {
return function(data) {
return mapObject(data, fn);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment