Last active
March 17, 2021 19:12
-
-
Save amsterdamharu/c08b45d9466c36ff04a8e9c0b90fa103 to your computer and use it in GitHub Desktop.
partial application
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
//using partially applied functions (closures) are used for sorting to | |
// demonstrate how to prevent duplicate implementation and writing | |
// imperative code. Most of your code should express "what it does" | |
// not "how it does it" | |
const data1 = [ | |
{ id: 1, name: 'a', items: [1, 2, 3] }, | |
{ id: 2, name: 'a', items: [1, 2] }, | |
{ id: 3, name: 'b', items: [4, 5] }, | |
{ id: 4, name: 'b', items: [4, 5, 6, 7] }, | |
]; | |
//stringCompare compares 2 strings | |
const stringCompare = (a, b) => a.localeCompare(b); | |
//numberCompare compares 2 numbers | |
// a sort callback should return 0,1 or -1 but returning a - b would also work | |
// JavaScript is not that strict and you will usually see something like this | |
// [5,4,3,2].sort((a,b)=>a-b) | |
const numberCompare = (a, b) => (a - b === 0 ? 0 : a - b < 0 ? -1 : 1); | |
//lengthCompare compares 2 arrays (objects with length property) | |
const lengthCompare = (a, b) => numberCompare(a.length, b.length); | |
//createComparer receives a comparer function and will use that to compare 2 objects | |
// const compareString = createComparer((a,b)=>a.localeCompare(b)) | |
//compareString receives a getter, lets say we want to get the name property | |
// of an object | |
// const compareStringName = compareString(item=>item.name) | |
//compareStringName receives a direction ascending = 1 and descending is -1 | |
// const compareStringNameAscending = compareStringName(1) | |
//compareStringNameAscending recieves a and b and will return 1 or -1, | |
// this can be used for sorting: data.sort(compareStringNameAscending) | |
const createComparer = (comparer) => (getter) => (direction) => (a, b) => | |
comparer(getter(a), getter(b)) * direction; | |
//creates a comparer that defaults to number compare | |
const createCompareNumber = createComparer(numberCompare); | |
//creates a comparer that defaults to string compare | |
const createCompareString = createComparer(stringCompare); | |
//creates a comparer that defaults to length compare | |
const createCompareLength = createComparer(lengthCompare); | |
//createSorter receives an array of sorter functions and returns a single | |
// sort function: const sort = createSorter([sortByName,sortByItemsAmount]) | |
//sort is a funcion that first sorts by name and then by item amount | |
// const otherSort = createSorter([sortByItemsAmount, sortByName]) | |
//otherSort is a function that first sorts by item amount and then by name | |
const createSorter = (sorters = []) => (a, b) => | |
sorters.reduce( | |
(result, sorter) => | |
//only use next sorter if previous sorter function returns 0 | |
// sorter returning 0 means both values are the same: | |
// 1 - 1 results in 0 "a".localeCompare("a") results in 0 | |
// if any sorter returns a non 0 value then use that value | |
result === 0 ? sorter(a, b) : result, | |
0 //initial sort value is 0 | |
); | |
//getter to get items from object | |
const getItems = ({ items }) => items; | |
//getter to get name from object | |
const getName = ({ name }) => name; | |
//compare name | |
const compareName = createCompareString(getName); | |
//compare items.lenght | |
const compareItemLength = createCompareLength(getItems); | |
//using the methods | |
//log helper to reduce repeating code | |
const createLog = (array) => (message, comparers, mapper = (x) => x) => | |
console.log( | |
message, | |
//sort mutates the array so make a shallow copy before sort | |
[...array].sort(createSorter(comparers)).map(mapper) | |
); | |
const log1 = createLog(data1); | |
log1('sort data by name and then by items.length both ascending', [ | |
compareName(1), | |
compareItemLength(1), | |
]); | |
log1('sort data by name ascending and then by items.length descending', [ | |
compareName(1), | |
compareItemLength(-1), | |
]); | |
log1('sort data by items.length descending and then by name ascending', [ | |
compareItemLength(-1), | |
compareName(1), | |
]); | |
//Can you implement sort by the highest item in items by making a function | |
// called compareHighestItem | |
//from here on we will add little new "how to" code, the functions created | |
// will re use other functions to create new behavior. The code shows | |
// little of "how it does it" and expresses more "what it does" | |
// declerative versus imperative. | |
//new "how to"; how to get the max number from an array of numbers | |
const max = (array) => Math.max(...array); | |
//re using pre existing "how to", code expresses "what it does", not | |
// "how to do it" | |
const maxCompare = (a, b) => numberCompare(max(a), max(b)); | |
const compareHighestItem = createComparer(maxCompare)(getItems); | |
log1( | |
'sort by highest item in items in ascending order using a different comparer', | |
[compareHighestItem(1)], | |
(item) => ({ ...item, highest: max(getItems(item)) }) | |
); | |
//Can you implement sort by highest item in items by making a function | |
// called getHighest? | |
//This is imperative code as it never expresses "how to" do things but only | |
// what it's doing. | |
//getHighest uses getter to get an array of numbers from object and then uses | |
// max to get the highest number of that numbers array | |
const getHighest = (getter) => (object) => max(getter(object)); | |
const compareHighest = createCompareNumber(getHighest(getItems)); | |
log1( | |
'sort by highest item in items in ascending order using a different getter', | |
[compareHighest(1)], | |
(object) => ({ | |
...object, | |
highest: getHighest(getItems)(object), | |
}) | |
); | |
//can you implement sorting by average in items by creating a function called | |
// getAverage? | |
//new "how to" here; how to sum up an array of numbers | |
const sum = (items) => items.reduce((result, item) => result + item, 0); | |
//new "how to" here; get the average of an array of numbers | |
const average = (items) => sum(items) / items.length; | |
//back to imperative code, only combining existing code | |
const getAverage = (getter) => (object) => average(getter(object)); | |
const compareAverage = createCompareNumber(getAverage(getItems)); | |
log1( | |
'sort by average items in ascending order using a different getter', | |
[compareAverage(1)], | |
(object) => ({ | |
...object, | |
average: getAverage(getItems)(object), | |
}) | |
); | |
//now we want to sort other data that does not have items but scores and scores | |
// contains objects that have points and difficulty with very little effort we | |
// can re use much of the logic we already created | |
const data2 = [ | |
{ | |
id: 1, | |
name: 'a', | |
scores: [ | |
{ points: 2, difficulty: 3 }, | |
{ points: 2, difficulty: 3 }, | |
], | |
}, | |
{ | |
id: 2, | |
name: 'a', | |
scores: [ | |
{ points: 2, difficulty: 2 }, | |
{ points: 1, difficulty: 1 }, | |
], | |
}, | |
{ | |
id: 3, | |
name: 'b', | |
scores: [{ points: 5, difficulty: 1 }], | |
}, | |
{ | |
id: 4, | |
name: 'b', | |
scores: [{ points: 5, difficulty: 2 }], | |
}, | |
]; | |
const log2 = createLog(data2); | |
//Can you implement sorting by highest individual score item of scores where | |
// a score is calculated as score.difficulty * score.points with a function | |
// called getScores? | |
const getScores = (object) => | |
//new "how to" here, this is the only line of code that defines | |
// how to do things. In this case how to convert an array of data2 | |
// item scores to an array of numbers | |
object.scores.map((score) => score.difficulty * score.points); | |
const compareHighestIndividual = createCompareNumber(getHighest(getScores)); | |
log2( | |
'sort by highest individual score descending', | |
[compareHighestIndividual(-1)], | |
(object) => ({ | |
...object, | |
highestIndividual: getHighest(getScores)(object), | |
}) | |
); | |
//Can you implement sorting by total score by creating a funtion called | |
// getTotalScore? | |
//Here it's done by no new "how to" at all, only re using | |
// "how to" that already exist. Creating code that only expresses | |
// "what to do". | |
const getTotatalScore = (object) => sum(getScores(object)); | |
const compareHighestTotal = createCompareNumber(getTotatalScore); | |
log2('sort by total score descending', [compareHighestTotal(-1)], (object) => ({ | |
...object, | |
totalScore: getTotatalScore(object), | |
})); | |
//Can you implement sorting by average score by creating a | |
// funtion called getAverageScore? | |
//As with getTotatalScore; all "how to" already exist | |
// this code is just about combining pre existing functions | |
const getAverageScore = (object) => average(getScores(object)); | |
const compareHighestAverage = createCompareNumber(getAverageScore); | |
log2( | |
'sort by average score descending', | |
[compareHighestAverage(-1)], | |
(object) => ({ | |
...object, | |
average: getAverageScore(object), | |
}) | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment