Automasean

Book notes: Functional-Light JavaScript

These are the raw notes that I took while reading Functional-Light JavaScript by Kyle Simpson.

JavaScript implementations of common functions used in functional programming


Unary

function unary(fn) {
return function onlyOneArg(arg) {
return fn(arg);
};
}

Identity

function identity(v) {
return v;
}

Can act as a truthy check when used as a predicate since JavaScript will coerce the value to true or false.

Example

identity('anything') ? doSomethingTruthy() : doSomethingFalsey();

Constant

function constant(v) {
return function value() {
return v;
};
}

Useful when you need a function to return an unchanging value.

Example

p1.then(foo)
.then(constant(unchangingVal))
.then(bar);

Apply

function spreadArgs(fn) {
return function spreadFn(argsArr) {
return fn(...argsArr);
};
}

Useful to transform an array argument into individual args.


Unapply

function gatherArgs(fn) {
return function gatheredFn(...argsArr) {
return fn(argsArr);
};
}

Useful to transform individual args into an array of args.


Partial

function partial(fn, ...presetArgs) {
return function partiallyApplied(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}

Reduces the arity of fn by creating another function where some args are preset.

Returns a function that, when invoked, will run fn with presetArgs and invokation args (laterArgs).

Example

[1, 2, 3, 4, 5].map(partial(add, 3)); // [4,5,6,7,8]

Partial Right

function partialRight(fn, ...presetArgs) {
return function partiallyApplied(...laterArgs) {
return fn(...laterArgs, ...presetArgs);
};
}

Useful when you want to partially apply args from the right instead of left.


Reverse Args

function reverseArgs(fn) {
return function argsReversed(...args) {
return fn(...args.reverse());
};
}

Currying

function curry(fn, arity = fn.length) {
return (function nextCurried(prevArgs) {
return function curried(nextArg) {
var args = [...prevArgs, nextArg];
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args);
}
};
})([]);
}

Currying unwinds a single higher-arity function into a series of chained unary functions. It's basically a special form of partial application where the arity is reduced to 1.

Note: If you use this implementation of curry(..) with a function that doesn't have an accurate length property, you'll need to pass the arity (the second parameter of curry(..)) to ensure curry(..) works correctly. The length property will be inaccurate if the function's parameter signature includes default parameter values, parameter destructuring, or is variadic with ...args.

function looseCurry(fn, arity = fn.length) {
return (function nextCurried(prevArgs) {
return function curried(...nextArgs) {
var args = [...prevArgs, ...nextArgs];
if (args.length >= arity) {
return fn(...args);
} else {
return nextCurried(args);
}
};
})([]);
}

Example

var curriedSum = looseCurry(sum, 5);
curriedSum(1)(2, 3)(4, 5); // 15

Currying/Partial Without Ordering Dependency (Named Props)

function partialProps(fn, presetArgsObj) {
return function partiallyApplied(laterArgsObj) {
return fn(Object.assign({}, presetArgsObj, laterArgsObj));
};
}
function curryProps(fn, arity = 1) {
return (function nextCurried(prevArgsObj) {
return function curried(nextArgObj = {}) {
var [key] = Object.keys(nextArgObj);
var allArgsObj = Object.assign({}, prevArgsObj, {
[key]: nextArgObj[key],
});
if (Object.keys(allArgsObj).length >= arity) {
return fn(allArgsObj);
} else {
return nextCurried(allArgsObj);
}
};
})({});
}

Example

function foo({ x, y, z } = {}) {
console.log(`x:${x} y:${y} z:${z}`);
}
var f1 = curryProps(foo, 3);
var f2 = partialProps(foo, { y: 2 });
f1({ y: 2 })({ x: 1 })({ z: 3 }); // x:1 y:2 z:3
f2({ z: 3, x: 1 }); // x:1 y:2 z:3

Note: we can only take advantage of currying with named arguments if we have control over the signature of foo(..) and define it to destructure its first parameter.


Complement

function not(predicate) {
return function negated(...args) {
return !predicate(...args);
};
}

When

function when(predicate, fn) {
return function conditional(...args) {
if (predicate(...args)) {
return fn(...args);
}
};
}

Compose

function compose2(fn2, fn1) {
return function composed(origValue) {
return fn2(fn1(origValue));
};
}

Most typical FP libraries define their compose(..) to work right-to-left in terms of ordering. The function calls are listed to match the order they are written in code manually or rather the order we encounter them when reading from left-to-right.

function compose(...fns) {
return function composed(result) {
// copy the array of functions
var list = [...fns];
while (list.length > 0) {
// take the last function off the end of the list
// and execute it
result = list.pop()(result);
}
return result;
};
}

Alternate implementation using recursion:

function compose(...fns) {
// pull off the last two arguments
var [fn1, fn2, ...rest] = fns.reverse();
var composedFn = function composed(...args) {
return fn2(fn1(...args));
};
if (rest.length == 0) return composedFn;
return compose(...rest.reverse(), composedFn);
}

Alternate implementation using reduce:

function compose(...fns) {
return fns.reverse().reduce(function reducer(fn1, fn2) {
return function composed(...args) {
return fn2(fn1(...args));
};
});
}

List Operations

var filter = curry((predicateFn, arr) => arr.filter(predicateFn));
var map = curry((mapperFn, arr) => arr.map(mapperFn));
var reduce = curry((reducerFn, initialValue, arr) =>
arr.reduce(reducerFn, initialValue)
);

You can create a generic "invoker" method to handle all 3:

var unboundMethod = (methodName, argCount = 2) =>
curry((...args) => {
var obj = args.pop();
return obj[methodName](...args);
}, argCount);
var filter = unboundMethod('filter', 2);
var map = unboundMethod('map', 2);
var reduce = unboundMethod('reduce', 3);

Other Notes

Other notes on concepts and vocabulary relating to functional programming and/or JavaScript.

Point Free Style (AKA Tactic Programming)

Point = Parameter

Point Free = Not explicitly passing a parameter to a unary function and letting the outer function implicitly pass the param to the inner function.

Example

function double(x) {
return x * 2;
}
[1, 2, 3, 4, 5].map(double); // [2,4,6,8,10]

Idempotence

From the mathematical point of view idempotence means an operation whose output won't ever change after the first call if you feed that output back into the operation over and over again.

Example

Math.abs(..)
Math.min(..)
Math.max(..)
Math.round(..)
Math.floor(..)
Math.ceil(..)

The programming-oriented definition for idempotence is similar, but less formal. Instead of requiring f(x) === f(f(x)), this view of idempotence is just that f(x); results in the same program behavior as f(x); f(x);. In other words, the result of calling f(x) subsequent times after the first call doesn't change anything.

Closure vs Objects

A closure associates a single function with a set of state whereas an object holding the same state can have any number of functions to operate on that state.

Reduce

Warning: In JavaScript, if there's not at least one value in the reduction (either in the array or specified as initialValue) an error is thrown. Be careful not to omit the initialValue if the list for the reduction could possibly be empty.

Beneficial Side Effects

Side effects may be useful for performance reasons like for caching.

Example

var specialNumber = (function memoization() {
var cache = [];
return function specialNumber(n) {
// if we've already calculated this special number,
// skip the work and just return it from the cache
if (cache[n] !== undefined) {
return cache[n];
}
var x = 1,
y = 1;
for (let i = 1; i <= n; i++) {
x += i % 2;
y += i % 3;
}
cache[n] = (x * y) / (n + 1);
return cache[n];
};
})();

The caching is useful here since passing a large number to specialNumber can be costly.

Freezing JavaScript Objects

Notable Object.freeze behavior:

var x = Object.freeze([2, 3, [4, 5]]);
// not allowed:
x[0] = 42;
// oops, still allowed:
x[2][0] = 42;

Vocabulary

A pure function has referential transparency which is an assertion that a function call could be replaced by its output value and the overall program behavior wouldn't change.

Isomorphism in JavaScript is when you have one set of JavaScript code that is converted to another set of JavaScript code and (importantly) you could convert from the latter back to the former if you wanted.

An array is a functor because it has a functor utility (map) that can perform an operation (operator function) on each member resulting in a transformed data structure.