Skip to content

Instantly share code, notes, and snippets.

@amsterdamharu
Last active March 17, 2021 19:12
Show Gist options
  • Save amsterdamharu/c08b45d9466c36ff04a8e9c0b90fa103 to your computer and use it in GitHub Desktop.
Save amsterdamharu/c08b45d9466c36ff04a8e9c0b90fa103 to your computer and use it in GitHub Desktop.
partial application
//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