Share
Embed
FKit: Everyday Functional Programming in JavaScript
In this talk I present a functional programming library I've been working on recently: FKit.
https://github.com/nullobject/fkit
Josh Bassett
October 08, 2014
Transcript
38 slides
-
Curried Functions - Like regular functions, but more delicious -
A curried function is a function that instead of taking multiple arguments takes exactly one argument. - It returns another function that takes exactly one argument, and so on. - When all the arguments are specified the result is returned. -
Curried Functions function add(a, b) { return a + b;
} function add(a) { return function(b) { return a + b; } } add(1); // function add(1)(2); // 3 -
Partially Applied Functions - What happens if we don't specify
all the arguments to a curried function? - Instead of a result, we get a partially applied function. -
Partially Applied Functions function mul(a) { return function(b) { return
a * b; } } var double = mul(2); double(1); // 2 double(2); // 4 double(3); // 6 -
Function Composition - Function composition is a powerful way to
specify a series of function applications. - Each function takes as an argument the result of the inner function. - Mathematically it is expressed: f . g 㱻 f(g(a)) -
Function Composition function compose(f, g) { return function(a) { return
f(g(a)); }; } var f = compose(add(1), mul(2)); f(1); // 3 f(2); // 5 f(3); // 7 -
FKIT - Provides many functions for solving common problems with
functions, objects, arrays, and strings. - Related to underscore, lodash, etc, but much more focussed on everyday utility. - Most functions are curried by default - Order of arguments to functions is natural. - Small: about 3 KB minified and gzipped! -
Numbers - FKit provides curried versions of most built-in numerical
operators and functions. -
Numbers F.add(1, 2); // 3 F.add(1)(2); // 3 F.sub(1, 2);
// 1 F.sub(1)(2); // 1 F.max(1, 2); // 2 F.max(1)(2); // 2 F.inc(1); // 2 F.dec(2); // 1 -
Predicates F.eq(1, 2); // false F.eq(2, 2); // true F.gt(1,
2); // true F.gt(2, 1); // false var p = F.gt(1); p(1); // false p(2); // true [1, 2, 3].filter(F.gt(1)); // [2, 3] -
Predicates var p = F.whereAll([F.gt(1), F.lt(3)]); p(1); // false p(2);
// true p(3); // false F.all(F.gt(1), [1, 2, 3]); // false F.any(F.gt(1), [1, 2, 3]); // true -
Lists - Both strings and arrays are treated as lists.
- This means you can use list combinators on both strings and arrays. - e.g. map, filter, fold, scan, zip, and many more. -
Lists - Yes, strings are lists! - “abc” => [‘a’,
‘b’, ‘c’] - [‘a’, ‘b’, ‘c’] => “abc” -
Lists [1, 2, 3].reverse(); // [3, 2, 1] 'hola'.reverse(); TypeError:
Object hola has no method ‘reverse’. F.reverse([1, 2, 3]); // [3, 2, 1] F.reverse('hola'); // 'aloh' -
Basics F.head([1, 2, 3]); // 1 F.head('foo'); // 'f' F.tail([1,
2, 3]); // [2, 3] F.tail('foo'); // 'oo' F.inits([1, 2, 3]); // [[], [1], [1, 2], [1, 2, 3]] F.inits('foo'); // ['', 'f', 'fo', 'foo'] F.tails([1, 2, 3]); // [[1, 2, 3], [2, 3], [3], []] F.tails('foo'); // ['foo', 'oo', 'o', ''] -
Transformations F.prepend(1, [2, 3]); // [1, 2, 3] F.prepend('f', 'oo');
// 'foo' F.append(3, [1, 2]); // [1, 2, 3] F.append('o', 'fo'); // 'foo' F.concat([1], [2, 3], [4, 5, 6]); // [1, 2, 3, 4, 5, 6] F.concat('f', 'oo', 'bar'); // 'foobar' -
Transformations F.intersperse(4, [1, 2, 3]); // [1, 4, 2, 4,
3] F.intersperse('-', 'hola'); // 'h-o-l-a' F.surround(0, 4, [1, 2, 3]); // [0, 1, 2, 3, 4] F.surround('¡', '!', 'hola'); // '¡hola!' -
Combinators F.map(F.inc, [1, 2, 3]); // [2, 3, 4] F.map(F.toUpper,
'foo'); // ['F', 'O', 'O'] F.fold(F.add, 0, [1, 2, 3]); // 6 F.fold(F.flip(F.prepend), '', 'foo'); // 'oof' F.filter(F.eq(2), [1, 2, 3]); // [2] F.filter(F.eq('o'), 'foo'); // 'oo' -
Combinators F.scan(F.add, 0, [1, 2, 3]); // [0, 1, 3,
6] F.scan(F.flip(F.prepend), 'foo'); // ['', 'f', 'of', 'oof'] F.zip([1, 2, 3], [4, 5, 6]); // [[1, 4], [2, 5], [3, 6]] F.zip('foo', 'bar'); // [['f', 'b'], ['o', 'a'], ['o', 'r']] -
Sublists F.take(2, [1, 2, 3]); // [1, 2] F.take(2, 'foo');
// 'fo' F.drop(2, [1, 2, 3]); // [3] F.drop(2, 'foo'); // 'o' F.split(F.eq(1), [1, 2, 3]); // [[1], [2, 3]] F.split(F.eq('a'), 'abc')); // ['a', 'bc'] F.group([1, 2, 2, 3, 3, 3]); // [[1], [2, 2], [3, 3, 3]] F.group('Mississippi'); // ['M', 'i', 'ss', 'i', 'ss', 'i', 'pp', 'i'] -
Sets F.nub([1, 2, 2, 3, 3, 3]); // [1, 2,
3] F.nub('abbccc'); // 'abc' F.union([1, 2, 3], [2, 3, 4]); // [1, 2, 3, 4] F.union('abc', 'bcd'); // 'abcd' F.intersect([1, 2, 3], [2, 3, 4]); // [2, 3] F.intersect('abc', 'bcd'); // 'bc' F.cartesian([1, 2], [3, 4]); // [[1, 3], [1, 4], [2, 3], [2, 4]] F.cartesian('ab', 'cd'); // [['a', 'c'], ['a', 'd'], ['b', 'c'], ['b', 'd']] -
Objects var shape = {type: 'circle', color: 'red'}; F.get('color', shape);
// 'red' F.set('color', 'green', shape); // {type: 'circle', color: 'green'} F.copy(shape, {color: 'blue', size: 1}); // {type: 'circle', color: 'blue', size: 1} -
Objects var shapes = [ {type: 'circle', color: 'red', size:
1}, {type: 'square', color: 'green', size: 2}, {type: 'triangle', color: 'blue', size: 3} ]; shapes.map(F.get('color')); // ['red', 'green', 'blue']; shapes.map(F.set('size', 100)); // [{…, size: 100}, {…, size: 100}, {…, size: 100}] -
Shootout #1 Calculate the maximum number in a list. i.e.
Given a list of as, find the largest a. -
Shootout #1 var as = [1, 2, 3]; as.reduce(Math.max.apply(Math, as));
// 3 Math.max.apply(null, as); // 3 F.fold(F.max, as[0], as); // 3 F.maximum(as); // 3 -
Shootout #2 Calculate the sums of the corresponding numbers in
two lists. The lists are not guaranteed to have the same length. i.e. Given lists of as and bs, for each pair [a, b] calculate a + b. -
Shootout #2 var as = [1, 2, 3], bs =
[4, 5, 6, 7]; var cs = []; for (var i = 0; i < Math.min(as.length, bs.length); i++) { cs.push(as[i] + bs[i]); } cs; // [5, 7, 9] F.zipWith(F.sum, as, bs); // [5, 7, 9] -
Shootout #3 Calculate the union of two lists. i.e. Given
lists of as and bs, calculate a list of cs such that it only contains distinct elements from as and bs. -
Shootout #3 var as = [1, 2, 3], bs =
[2, 3, 4]; var cs = []; as.reduce(function(cs, a) { if (cs.indexOf(a) < 0) { cs.push(a); } return cs; }, cs); bs.reduce(function(cs, b) { if (cs.indexOf(b) < 0) { cs.push(b); } return cs; }, cs); cs; // [1, 2, 3, 4] F.union(as, bs); // [1, 2, 3, 4] -
Shootout #4 Find the longest run of numbers in a
list. i.e. Given a list of as, calculate the longest contiguous sequence of equal elements. -
Shootout #4 var as = [1, 2, 2, 3, 3,
3]; as.reduce(function(bs, a) { if (bs[0] !== a) { bs = [a]; } else { bs.push(a); } return bs; }, []).length // 3; F.compose( F.maximum, F.map(F.length), F.group )(as); // 3