ref – http://chineseruleof8.com/code/index.php/2019/01/03/functions-are-first-class-objects-js/
function vs procedure
Procedure – collection of functionalities.
May or may not have input, or return values, and have multiple tasks.
Functions – Mathematical functions.
- Have input
- Return value
- Simplified to a single task
- Easy for reuse
Step 1
We first have a string literal.
We have a function called prepareString.
And by scope, it reference the str and trims it for str1.
Then str1 replaces it with regex for str2.
Finally str2 upper cases everything for str3.
str3 then splits into an array.
Finally we loop through this array and try to find A, AN, or THE. If its found, delete it using splice.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
let str = 'Innovation distinguishes between a leader and a follower.'; let prepareString = function() { let str1 = str.trim(); let str2 = str1.replace(/[?.,!]/g,''); let str3 = str2.toUpperCase(); let arr = str3.split(" "); for (let i = 0; i < arr.length; i++) { if (arr[i] === 'A' || arr[i] === 'AN' || arr[i] === 'THE') { arr.splice(i,1); } } return arr; }; |
Step 2 – separate them into one task functions
1 |
let str1 = str.trim(); |
becomes
1 2 3 |
const trim = function(str) { return str.replace(/^\s*|\s*$/g, ''); } |
1 |
let str2 = str1.replace(/[?.,!]/g,''); |
becomes
1 2 3 |
const noPunct = function(str) { return str.replace(/[?.,!]/g,''); } |
1 |
let str3 = str2.toUpperCase(); |
becomes
1 2 3 |
const capitalize = function(str) { return str.toUpperCase(); } |
1 |
let arr = str3.split(" "); |
becomes
1 2 3 |
const breakout = function(str) { return str.split(" "); } |
1 |
if (arr[i] === 'A' || arr[i] === 'AN' || arr[i] === 'THE') |
becomes
1 2 3 |
const noArticles = function(str) { return (str !== "A" && str !== "AN" && str !== "THE"); } |
Using noArticles:
1 2 3 4 5 |
for (let i = 0; i < arr.length; i++) { if (arr[i] === 'A' || arr[i] === 'AN' || arr[i] === 'THE') { arr.splice(i,1); } } |
becomes
1 2 3 |
const filterArticles = function(arr) { return arr.filter(noArticles); } |
Step 3
Then we need to change these functions into arrow functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
str = 'Innovation distinguishes between a leader and a follower.'; const trim = str => str.replace(/^\s*|\s*$/g, ''); const noPunct = str => str.replace(/[?.,!]/g,''); const capitalize = str => str.toUpperCase(); const breakout = str => str.split(" "); const noArticles = str => (str !== "A" && str !== "AN" && str !== "THE"); const filterArticles = arr => arr.filter(noArticles); |
Step 4 – Optionally call them together
1 |
console.log(filterArticles(breakout(capitalize(noPunct(trim(str)))))); |
pipe
The pipe function goes through each function and applies the accumulator string to each function..starting with the initiator x.
So if x is ‘word’, and the first function is !${param}
, this will return ‘!word’.
Then the 2nd function is *${param}
, then we’d get ‘*!word’.
Basically, it would apply the result of the current function to the param of the next function.
Notice our spread operators in the parameter. Its taking all the params together and bringing them together as the array fns.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
const pipe = function(...fns) { return function(x) { const result = fns.reduce(function(v, f) { // f is the function that returns a string // v is the accumulated string. const res = f(v); return res; }, x); // x is the starting string return result; } }; const money = param => `$${param}`; const exclamation = param => `!${param}`; const wrapper = param => `(${param})`; const star = param => `*${param}`; const rightArrow = param => `${param}-->`; const leftArrow = param => `<--${param}`; // notice how it is much easier to read const result = pipe( money, exclamation, wrapper, star, rightArrow, leftArrow); result('word'); |
Our output would be:
$word
!$word
(!$word)
*(!$word)
*(!$word)–>
<--*(!$word)-->
We can also start from the right function, (by using reduceRight) so now our output would like this:
<--word
<--word-->
*<--word-->
(*<--word-->)
!(*<--word-->)
$!(*<--word-->)
Now, going back to our work, we would stick each function to process on the string. Then use the returned string as the input for the next function. Literally, the functions put together act as a pipe for strings to go through and be processed.
1 2 3 4 5 6 |
const prepareString = pipe( trim, noPunct, capitalize, breakout, filterArticles); |
Function Composition 2
Given…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const pipe = function(...fns) { return function(x) { return fns.reduce(function(v, f) { return f(v); }, x); } }; const compose = function(...fns) { return function(x) { return fns.reduceRight(function(v, f) { return f(v); }, x); } }; |
and situation is:
1 2 3 4 5 6 |
const scores = [50, 6, 100, 0, 10, 75, 8, 60, 90, 80, 0, 30, 110]; const boostSingleScores = scores.map(val => (val < 10) ? val * 10 : val); const rmvOverScores = boostSingleScores.filter(val => val <= 100); const rmvZeroScores = rmvOverScores.filter(val => val > 0); const scoresSum = rmvZeroScores.reduce((sum, val) => sum + val, 0); const scoresCnt = rmvZeroScores.reduce((cnt, val) => cnt + 1, 0); |
1) Convert each statement to a function that can accept and act on any array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const scores = [50, 6, 100, 0, 10, 75, 8, 60, 90, 80, 0, 30, 110]; const singleScoreByTen = function (arr) { return arr.map(val => (val < 10) ? val * 10 : val); } const rmvOverScores = function(arr) { return arr.filter(val => val <= 100); } const rmvZeroScores = function(arr) { return arr.filter(val => val > 0); } const scoresSum = function(arr) { return arr.reduce((sum, val) => sum + val, 0); } const scoresCnt = function(arr) { return arr.reduce((cnt, val) => cnt + 1, 0); } |
2) Compose a function that will remove both zero or lower scores and scores over 100. Test it using the scores array.
1 2 |
const removeZeroAndOver = pipe(rmvOverScores, rmvOverScores); removeZeroAndOver(scores); |
3) Compose a function that will do all the modifications to an array. Test it using the scores array.
1 2 |
const allMod = pipe(removeZeroAndOver, singleScoreByTen); allMod(scores); |
4) Create a function that will accept an array and return the average. Use the function that sums scores and the function that counts scores or the length property.
1 |
const average = (scores) => scoresSum(scores) / scores.length; |
5) Compose a function that will do all the modifications on an array and return an average.
1 2 |
const average = pipe(allMod, scoresCnt); average(scores); |
Arity
So we still have our pipe function. Remember that it takes in an array of functions, and an initial string x. (Henry)
It goes through the first function and gives ‘Henry’ as the first parameter. The return value of the 1st function (user object) is then inserted into the 2nd function.
The return value of the 2nd function is then inserted into the 3rd function, so on and so forth.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const pipe = function(...fns) { console.log(`We are going to go through ${fns.length} functions`); return function(x) { console.log(`starting variable is ${x}`); const result = fns.reduce(function(result, f) { // f is the function that returns a string // result is the initial input, and each subsequent input. console.log(`-- ${f.name} -- input: `, result); const res = f(result); console.log('returned', res); return res; }, x); // x is the starting string return result; } }; |
users array is the data that will be bind to getUsers function.
1 |
const users = [{name: "James",score: 30,tries: 1}, {name: "Mary", score: 110,tries: 4}, {name: "Henry",score: 80,tries: 3}]; |
As you can see we a function like so:
1 2 3 4 5 6 7 8 9 10 |
var getUser = function(arr, name) { return arr.reduce(function(obj, val) { if (val.name.toLowerCase() === name.toLowerCase()) { console.log(`found ${val.name} object!`); return val; } else { return obj; } }, null); }; |
We need to change it using bind so that we can pass in users as the data, and we get a reference to it all. That way, users is matched up to param arr.
1 |
const partGetUser = getUser.bind(null, users); // we pass in users array and point a reference to it. |
We get partGetUser, and then when we execute it inside pipe, we can pass in ‘Henry’ to param name of getUser.
storeUser is just a standard function.
1 2 3 4 5 6 7 8 9 10 |
//Modifies Data var storeUser = function(arr, user) { return arr.map(function(val) { if (val.name.toLowerCase() === user.name.toLowerCase()) { return user; } else { return val; } }); }; |
we have our cloneObj that takes an object and clones it using JSON. We leave it as is.
1 2 3 4 5 6 |
//Pure Functions const cloneObj = function(obj) { const clonedObj = JSON.parse(JSON.stringify(obj)); console.log(`cloned ${obj.name}`, clonedObj); return clonedObj; }; |
Then we have updateScore:
1 2 3 4 5 6 7 |
var updateScore = function(newAmt, user) { console.log(`Lets update score in ${user.name} to ${newAmt}`); if (user) { user.score += newAmt; return user; } }; |
we need to change it like this:
1 2 3 |
// const usr1 = updateScore(cloneObj(usr), 30); const partUpdateScore30 = updateScore.bind(null, 30); // we pass in the numbers, and point a reference to it. // that way, we pass in an object into the 3rd parameter later. |
updateTries is used as is.
1 2 3 4 5 6 7 8 9 10 |
var updateTries = function(user) { if (user) { user.tries++; return user; } }; // const usr2 = updateTries(cloneObj(usr1)); // const newArray = storeUser(users, usr2); |
We put updateUser, cloneObj, partUpdateScore30, and updateTries into our pipe utility function.
We get the result back when ‘Henry’ is inserted into the beginning of the pipe.
1 2 3 4 5 6 7 8 |
const updateUser = pipe( partGetUser, // we already use bind to pass in users. When executed, the string will come in as "Henry". We find "Henry" obj and pass it into cloneObj cloneObj, // then with the result, we execute cloneObj and return the cloned object. partUpdateScore30, // we then pass the cloned Object in and execute partUpdateScore30 with the obj as the next parmater after 30. It returns the user obj updateTries); // the user obj goes into the unary parameter, updates tries, and then returns the user object as result. const result = updateUser("Henry"); console.log(result); |
Arity full code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
const pipe = function(...fns) { console.log(`We are going to go through ${fns.length} functions`); return function(x) { console.log(`starting variable is ${x}`); const result = fns.reduce(function(result, f) { // f is the function that returns a string // result is the initial input, and each subsequent input. console.log(`-- ${f.name} -- input: `, result); const res = f(result); console.log('returned', res); return res; }, x); // x is the starting string return result; } }; const users = [{name: "James",score: 30,tries: 1}, {name: "Mary", score: 110,tries: 4}, {name: "Henry",score: 80,tries: 3}]; //Modifies Data var storeUser = function(arr, user) { return arr.map(function(val) { if (val.name.toLowerCase() === user.name.toLowerCase()) { return user; } else { return val; } }); }; //Pure Functions const cloneObj = function(obj) { const clonedObj = JSON.parse(JSON.stringify(obj)); console.log(`cloned ${obj.name}`, clonedObj); return clonedObj; }; var getUser = function(arr, name) { return arr.reduce(function(obj, val) { if (val.name.toLowerCase() === name.toLowerCase()) { console.log(`found ${val.name} object!`); return val; } else { return obj; } }, null); }; var updateScore = function(newAmt, user) { console.log(`Lets update score in ${user.name} to ${newAmt}`); if (user) { user.score += newAmt; return user; } }; var updateTries = function(user) { if (user) { user.tries++; return user; } }; // const usr = getUser(users, "Henry"); const partGetUser = getUser.bind(null, users); // we pass in users array and point a reference to it. // that we, we pass a string name into the 3rd parameter later // const usr1 = updateScore(cloneObj(usr), 30); const partUpdateScore30 = updateScore.bind(null, 30); // we pass in the numbers, and point a reference to it. // that way, we pass in an object into the 3rd parameter later. // const usr2 = updateTries(cloneObj(usr1)); // const newArray = storeUser(users, usr2); const updateUser = pipe( partGetUser, // we already use bind to pass in users. When executed, the string will come in as "Henry". We find "Henry" obj and pass it into cloneObj cloneObj, // then with the result, we execute cloneObj and return the cloned object. partUpdateScore30, // we then pass the cloned Object in and execute partUpdateScore30 with the obj as the next parmater after 30. It returns the user obj updateTries); // the user obj goes into the unary parameter, updates tries, and then returns the user object as result. const result = updateUser("Henry"); console.log(result); |