Last active
April 17, 2018 03:14
-
-
Save andrewthauer/a163ecc784f4d3e2619d236638c43cff to your computer and use it in GitHub Desktop.
FP Intro with JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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))) //? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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