Skip to content

Instantly share code, notes, and snippets.

@iamstarkov
Last active November 25, 2016 14:18
Show Gist options
  • Select an option

  • Save iamstarkov/da273bbf51f97a5a4fd3f1fab09798dd to your computer and use it in GitHub Desktop.

Select an option

Save iamstarkov/da273bbf51f97a5a4fd3f1fab09798dd to your computer and use it in GitHub Desktop.
/*
sup /js/
i wanna share some functional programming knowledge in application to JS tooling,
more specially bundling (not really, but close).
I will try to cover some fp topics, like:
* why does it mean "functional"
* pureness, side-effects
* what is currying
* what is composition
* a bit of lenses
what im not going to tell about
* monads, functors, applicative functors: nope
I'm planning to make a workshop series to describe all of that. To make sure
that anything mentioned before make sense, we all will implement real world js tool
which can be used to build bundlers like browserify or rollup.
*/
const splitByComma = {
naive: input => input.split(','), // oh my god, no, no… no!
typecheck: input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input.split(',');
// error prone
}
edgecase: input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input.split(',').filter(Boolean);
// better, though more edgecases left
}
edgecase2: input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input.split(',').map(_ => _.trim()).filter(_ => typeof _ !=== 'undefined');
// best in terms of edgecases, worst in readability and debugging
}
readable: input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
var inputArray = input.split(',');
var trimmedArray = inputArray.map(_ => _.trim());
var filteredArray = trimmedArray.filter(_ => typeof _ !=== 'undefined');
return filteredArray;
// best in terms of edgecases
// debug-friendly
// readable
// but tedious and repetitive
// also input validation is not in line with everything else
}
validateAsFn: input => {
const validate = input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
var validInput = validate(input);
var inputArray = validInput.split(',');
var trimmedArray = inputArray.map(_ => _.trim());
var filteredArray = trimmedArray.filter(_ => typeof _ !=== 'undefined');
return filteredArray;
// input validation is in line with everything else
// a lot of temp variables
}
draftPipe: input => {
const validate = input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
const fn = pipe(
input => validate(input),
validInput => input.split(','),
inputArray => inputArray.map(_ => _.trim()),
trimmedArray => trimmedArray.filter(_ => typeof _ !=== 'undefined')
);
return fn(input);
// better, but do we really need all those variables
}
draftPipeV2: input => {
const validate = input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
const fn = pipe(
input => validate(input),
validInput => input.split(','),
inputArray => inputArray.map(_ => _.trim()),
trimmedArray => trimmedArray.filter(_ => typeof _ !=== 'undefined')
);
return fn(input);
// you will ask me about debuggin this shit, right?
}
draftPipeV3debugwise: input => {
const tap = (fn, input) => { fn(input); return input; }
const log = input => tap(console.log.bind(this), input);
const validate = input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
const fn = pipe(
validate,
validInput => split(',', validInput),
log,
inputArray => inputArray.map(_ => _.trim()),
trimmedArray => trimmedArray.filter(_ => typeof _ !=== 'undefined')
);
return fn(input);
// hey, kind of readable and with debuggin!
// yes, but whats about all of that temp variables?
// lets try one approach to remove them
// but first lets try to simplify stuff
}
draftPipeV4moreFunctions: input => {
const tap = (fn, input) => { fn(input); return input; }
const log = input => tap(console.log.bind(this), input);
const validate = input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
const split = (separator, input) => input.split(separator);
const trim = input => input.trim();
const isNil = input => typeof input === 'undefined';
const fn = pipe(
validate,
validInput => split(',', validInput),
log,
inputArray => inputArray.map(trim),
trimmedArray => trimmedArray.filter(isNil)
);
return fn(input);
// a bit cleaner
// yes, but still whats about all of that temp variables?
// wait a bit one more step required
}
draftPipeV5moreFunctionsToTheFunctionsGod: input => {
const tap = (fn, input) => { fn(input); return input; }
const log = input => tap(console.log.bind(this), input);
const validate = input => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
const split = (separator, input) => input.split(separator);
const trim = input => input.trim();
const isNil = input => typeof input === 'undefined';
const map = (fn, arr) => arr.map(fn);
const filter = (fn, arr) => arr.filter(fn);
const fn = pipe(
validate,
validInput => split(',', validInput),
log,
inputArray => map(trim, inputArray),
trimmedArray => filter(isNil, trimmedArray)
);
return fn(input);
// now we are ready to add some curry
}
draftPipeV6currying: input => {
const tap = (fn, input) => { fn(input); return input; }
const log = input => tap(console.log.bind(this), input);
const validate = input, => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
// simple auto currying
// (does NOT separately handle f.length == args.length or f.length < args.length cases)
const curry = (f, ...args) => (f.length <= args.length)
? f(...args)
: (...more) => curry(f, ...args, ...more);
const split = curry((separator, input) => input.split(separator));
const trim = input => input.trim();
const isNil = input => typeof input === 'undefined';
const map = curry((fn, arr) => arr.map(fn));
const filter = curry((fn, arr) => arr.filter(fn));
const fn = pipe(
validate,
split(','),
log,
map(trim),
filter(isNil)
);
return fn(input);
// now we are ready to extract apply currying!
}
dataflowContract: input => {
const tap = (fn, input) => { fn(input); return input; }
const log = input => tap(console.log.bind(this), input);
const contract = (argName, type, arg) => {
if (typeof input !== 'string') {
throw new Error('input should be String, got: ' + input);
}
return input;
}
const pipe = (...args) => result => args.reduce((res, arg) => arg.call(this, res), result);
// simple auto currying
// (does NOT separately handle f.length == args.length or f.length < args.length cases)
const curry = (f, ...args) => (f.length <= args.length)
? f(...args)
: (...more) => curry(f, ...args, ...more);
const split = curry((separator, input) => input.split(separator));
const trim = input => input.trim();
const isNil = input => typeof input === 'undefined';
const map = curry((fn, arr) => arr.map(fn));
const filter = curry((fn, arr) => arr.filter(fn));
const fn = pipe(
validate,
split(','),
log,
map(trim),
filter(isNil)
);
return fn(input);
// now we are ready to extract apply currying!
}
// extract everything and result will look like this
dataflow: pipe(
validate, // validate input
split(','), // split
map(trim), // trim array
filter(isEmpty) // filter array
) // readable, debuggable, all edgecases covered and declarative
// look ma,
// no variables, no mutation!
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment