Skip to content

Instantly share code, notes, and snippets.

@andrewthauer
Last active April 17, 2018 03:14
Show Gist options
  • Save andrewthauer/a163ecc784f4d3e2619d236638c43cff to your computer and use it in GitHub Desktop.
Save andrewthauer/a163ecc784f4d3e2619d236638c43cff to your computer and use it in GitHub Desktop.
FP Intro with JS
// Imperative
let numbers = [];
for(let i = 0; i < 10; i++) {
numbers.push(i);
}
numbers //?
// Declaritive
;[...Array(10).keys()] //?
// ---------------------------------------------------
// Airty
//
// The number of arguments a function takes. From words like unary, binary, ternary, etc.
//
// unary airty
const increment = (a) => a + 1
increment.length //? 1
// binary airty
const add = (a, b) => a + b
add.length //? 2
add(1, 1) //?
// ---------------------------------------------------
// Identity
//
// A function that preserves the identify of the value passed in
//
const identity = (a) => a;
identity(true) //? true
identity(400) //? 400
identity({ a: 1, b: 2 }) //?
// ---------------------------------------------------
// Predicate
//
// A unary function that takes an argument and returns true or false.
//
const isTruthy = (value) => !!value
isTruthy(false) //? false
isTruthy(null) //? false
isTruthy(undefined) //? false
isTruthy('') //? false
isTruthy(true) //? true
isTruthy('foo') //? true
isTruthy(3) //? true
isTruthy(0) //? true
isTruthy({}) //? true
isTruthy([]) //? true
isTruthy(NaN) //? true
// ---------------------------------------------------
// Higher-Order Functions (HOF)
//
// A function which takes a function as an argument and/or returns a function.
// Filter
const filter = (predicate, arr) => arr.filter(predicate)
const isEven = (x) => x % 2 === 0
filter(isEven, Array.of(1, 2, 3, 4)) //? [2, 4]
// Multiple
const is = (type) => (x) => Object(x) instanceof type
const isNumberAndEven = (x) => is(Number)(x) && isEven(x)
is(Number)('2') //?
filter(isNumberAndEven, [1, '2', 3, 4].map(increment)) //?
// ---------------------------------------------------
// Foldable
//
// An object that has a reduce function that can transform that object into some other type.
//
const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6
// ---------------------------------------------------
// Partial Application
//
// Partially applying a function means creating a new function by pre-filling some of the arguments to the original function.
//
// Simple partial application
const multiplyBy = (a) => (b) => a * b;
const double = multiplyBy(2);
double(10) //? 20
// Partial function definition
const partial = (fn, ...args) => (...moreArgs) => fn(...args, ...moreArgs)
// Multiple arguments
const add3 = (a, b, c) => a + b + c
const fivePlus = partial(add3, 2, 3)
fivePlus(4) //? 9
// You can also use Function.prototype.bind to partially apply a function in JS:
const add1More = add3.bind(null, 2, 3) //? (c) => 2 + 3 + c
add1More(4) //? 9
// ---------------------------------------------------
// Currying
//
// The process of converting a function that takes multiple arguments into a function that takes them one at a time.
// Each time the function is called it only accepts one argument and returns a function that takes one argument until all arguments are passed
//
// Manually currying
const curriedSum = (a) => (b) => a + b
curriedSum(40)(2) //? 42
const add2 = curriedSum(2) // (b) => 2 + b
add2(10) //? 12
// Basic one-argument currying
const curry = (x) => (y, ...args) => (args.length === 0) ? partial(x, y) : x(y, ...args);
// Example
const curriedMultply = curry((a, b) => a + b)
curriedMultply(8)(2) //? 16
const cubed = curriedMultply(3) // (b) => 3 * n
cubed(3) //? 9
// ---------------------------------------------------
// Auto Currying
//
// Transforming a function that takes multiple arguments into one that if given less than its correct number of
// arguments returns a function that takes the rest. When the function gets the correct number of arguments
// it is then evaluated.
//
// Auto curried function
const curryN = (fn) => {
return function curryFn(...args1) {
if (args1.length >= fn.length) {
return fn(...args1);
} else {
return (...args2) => curryFn(...args1, ...args2);
}
}
}
const add3C = curryN(add3)
add3C(1)(2)(3) //?
add3C(1, 2)(3) //?
add3C(1)(2, 3) //?
add3C(1, 2, 3) //?
// ---------------------------------------------------
// Function composition
//
// The act of putting two functions together to form a third function where the output of one function is the input of the other.
//
// Definition
const compose = (f, g) => (a) => f(g(a))
// Definition right to left
const composeN = (...fns) => x => fns.reduceRight((y, f) => f(y), x)
// Evaluate left to right
const pipeN = (...fns) => x => fns.reduce((y, f) => f(y), x)
// Examples
compose(increment, double)(10) //? 21
composeN(increment, double)(10) //? 21
pipeN(double, increment)(10) //? 21
compose(double, increment)(10) //? 22
composeN(double, increment)(10) //? 22
pipeN(increment, double)(10) //? 22
// ---------------------------------------------------
// Point-Free
//
// Writing functions where the definition does not explicitly identify the arguments used. This style usually
// requires currying or other Higher-Order functions. A.K.A Tacit programming.
//
// Given
const map = (fn) => (list) => list.map(fn)
const addC = (a) => (b) => a + b
// Not points-free - `numbers` is an explicit argument
const incrementAll = (numbers) => map(addC(1))(numbers)
incrementAll([1, 2, 3]) //?
// Points-free - The list is an implicit argument
const incrementAll2 = map(addC(1))
incrementAll2([1, 2, 3]) //?
// ---------------------------------------------------
// Functor
//
// An object that implements a map function which, while running over each value in the object
// to produce a new object, adheres to two rules: Preservices identity & is composable
//
// An array is a Functor
//
;[1, 2, 3].map(x => x) //?
// Functor
;[1, 2, 3].map(x => increment(double(x))) //?
;[1, 2, 3].map(double).map(increment) //?
// ---------------------------------------------------
// Applicative Functor
//
// An applicative functor is an object with an ap function. ap applies a function in the object to a value in another object of the same type.
//
// Array Implementation
Array.prototype.ap = function (xs) {
return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
// Example usage
;[(a) => a + 1].ap([1]) //? [2]
// ---------------------------------------------------
// Pointed Functor
//
// An object with an of function that puts any single value into it.
//
// ES2015 adds Array.of making arrays a pointed functor.
Array.of(2) //?
// ---------------------------------------------------
// Lifting
//
// Lifting is when you take a value and put it into an object like a functor. If you lift a function into an
// Applicative Functor then you can make it work on values that are also in that functor.
//
// Some implementations have a function called lift, or liftA2 to make it easier to run functions on functors.
const liftA2 = (f) => (a, b) => a.map(f).ap(b) // note it's `ap` and not `map`.
const mult = a => b => a * b
const liftedMult = liftA2(mult) // this function now works on functors like array
liftedMult([1, 2], [3]) //? [3, 6]
liftA2(a => b => a + b)([1, 2], [3, 4]) //? [4, 5, 5, 6]
// ---------------------------------------------------
// Monoid
//
// An object with a function that "combines" that object with another of the same type.
//
1 + 1 //?
1 + 1 // 2 2
1 + (2 + 3) === (1 + 2) + 3 //?
;[1, 2].concat([3, 4]) //? [1, 2, 3, 4]
;[1, 2].concat([]) //? [1, 2]
compose(increment, identity)(1) === compose(identity, increment)(1) //?
// ---------------------------------------------------
// Monad
//
// A monad is an object with of and chain functions. chain is like map except it un-nests the resulting nested object.
//
// A monad is a way of composing functions that require context in addition to the return value,
// such as computation, branching, or I/O. Monads type lift, flatten and map so that the types line
// up for lifting functions a => M(b), making them composable. It's a mapping from some type a to
// some type b along with some computational context, hidden in the implementation details of
// lift, flatten, and map:
//
// Functions map: a => b
// Functors map with context: Functor(a) => Functor(b)
// Monads flatten and map with context: Monad(Monad(a)) => Monad(b)
const x = 20 // Some data of type `a`
const listOfX = Array.of(x) // JS has type lift sugar for arrays: [x]
// .map() applies the function f to the value x in the context of the array.
const result = listOfX.map(double) //? [40]
// flatten
;[].concat.apply([], [[1], [2, 3], [4]]) //? [1, 2, 3, 4]
// g: a => b
// f: b => c
// h = f(g(a)): a => c
// const g = a => ({ b: 'b' })
// const f = b => ({ c: 'c' })
// a = { a: 1 }
// b = g(a) //?
// c = f(b) //?
// f(g((a))) //?
// JavaScript Monads Made Simple
// https://medium.com/javascript-scene/javascript-monads-made-simple-7856be57bfe8
// Functional Composition
// compose(f, g)(x) = (f ∘ g)(x) = f(g(x))
// A monad is a way of composing functions that require context in addition to the return value,
// such as computation, branching, or I/O. Monads type lift, flatten and map so that the types line
// up for lifting functions a => M(b), making them composable. It's a mapping from some type a to
// some type b along with some computational context, hidden in the implementation details of
// lift, flatten, and map:
// Functions map: a => b
// Functors map with context: Functor(a) => Functor(b)
// Monads flatten and map with context: Monad(Monad(a)) => Monad(b)
const x = 20; // Some data of type `a`
const f = n => n * 2; // A function from `a` to `b`
const arr = Array.of(x); // The type lift.
// JS has type lift sugar for arrays: [x]
// .map() applies the function f to the value x
// in the context of the array.
const result = arr.map(f); //? [40]
// or
[].concat.apply([], [[1], [2, 3], [4]]); //? [1, 2, 3, 4]
// compose definition
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
// trace utility
const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
// const getUserById = (id) => Promise(User)
// const hasPermision = (User) => Promise(Boolean)
{
// Identity monad
const Id = value => ({
// Functor mapping
// Preserve the wrapping for .map() by
// passing the mapped value into the type
// lift:
map: f => Id.of(f(value)),
// Monad chaining
// Discard one level of wrapping
// by omitting the .of() type lift:
chain: f => f(value),
// Just a convenient way to inspect
// the values:
toString: () => `Id(${value})`
});
// The type lift for this monad is just
// a reference to the factory.
Id.of = Id;
const g = n => Id(n + 1);
const f = n => Id(n * 2);
// Left identity
// unit(x).chain(f) ==== f(x)
trace('Id monad left identity')([Id(x).chain(f), f(x)]);
// Id monad left identity: Id(40), Id(40)
// Right identity
// m.chain(unit) ==== m
trace('Id monad right identity')([Id(x).chain(Id.of), Id(x)]);
// Id monad right identity: Id(20), Id(20)
// Associativity
// m.chain(f).chain(g) ====
// m.chain(x => f(x).chain(g)
trace('Id monad associativity')([
Id(x)
.chain(g)
.chain(f),
Id(x).chain(x => g(x).chain(f))
]);
// Id monad associativity: Id(42), Id(42)
} //?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment