Functional Fat-Arrow Function with Type Checking Using Ternary Operator
//Takes in two variables, then calls two math functions on them
const doubleOutcome = (x, y, firstMath, secondMath) => {
return ((typeof firstMath != "function" || typeof secondMath != "function")
?
"Type error"
:
secondMath(firstMath(x, y)));
}
const add = (x, y) => x + y;
const double = z => z * 2;
console.log(`Result after addition and multiplication: ${doubleOutcome(2, 3, add, double)}`);
Referential Transparency & Equational Reasoning
Pure functions have "referential transparency." This means a pure function can be substituted for it's evaluated value without changing the behavior of the program. As a result if a program has a firstFunction that produces firstData and secondFunction that takes in firstData, the code could be switched to have firstFunction defined inline inside of secondFunction, without causing any side effect on the program. This property of substitution of equals for equals is called "equational reasoning."
//immutable is an NPM package. Any output on from Map() becomes an immutable object.
const { Map } = require("immutable");
const chris = Map({name: 'Chris', age: 20, sign: 'Leo'});
const isLeo = someGuy => someGuy.get('sign') == 'Leo' ? `is` : `is not`;
const nameSign = someGuy => `${chris.get('name')} ${isLeo(chris)} a Leo`;
console.log(nameSign(chris)); //Chris is a Leo
The above script calls isLeo on a person, Chris. If Chris is a leo, it returns 'is'. If not, it returns 'is not'. It then prints the name of the Chris, combined with a call to 'isLeo', to state if Chris is a Leo or not. Since isLeo is a pure function, it can be defined inline inside of the nameSign function without resulting in any side effects or change in output given the same inputs. This is an example of equational reasoning and a benefit of functional programming and pure functions.
const { Map } = require("immutable");
const chris = Map({name: 'Chris', age: 20, sign: 'Leo'});
const nameSign = someGuy => `${chris.get('name')} ${chris.get('sign') == 'Leo' ? `is` : `is not`} a Leo`;
console.log(nameSign(chris)); //Chris is a Leo
Review of bind
let person = {
word: 'hello',
speak: function() {
console.log(this.word); //this reaches down to person scope
}
}
person.speak(); //'hello', since speak() has access to word as is within person scope
let speakFunction = person.speak; //'undefined', since current scope (window) does not have access to speak
speakFunction();
speakFunction = speakFunction.bind(person); //binds speakFunction to person scope
speakFunction(); //'hello', since speakFunction now bound to person scope
Composition
Composition is a programming pattern in which a function takes in functions as arguments and returns another function composed of the functions passed into it. You can then call this composed function and it will handle the execution chain of the functions it is composed of. You can also compose functions out of composed functions. Here is a simple example:
composedFunction = (functA, functB) =>
x => functA(functB(x));
const triple = x => x * 3;
const square = x => x * x;
const tripleSquare = composedFunction(triple, square);
console.log(tripleSquare(6)); //108
const uppercase = string => string.toUpperCase();
const intense = string => `${string}!!!!`;
const loud = composedFunction(uppercase, intense);
console.log(loud('This sentence is loud')); //THIS SENTENCE IS LOUD!!!!
The alternative would be to call the functions as seen below, passing function calls in as arguments. While this might make for shorter code, it also makes it much less flexible. What would happen if the name of the square function changed? Not only would you have to change the name of the function during its definition, but also the below calls to it as well. Now what would happen if you were importing a module that contained the square function in multiple other modules? You'd have to find every instance of the call to square and change all of those calls. By using a composed function, you could avoid such issues and build much easier to maintain code.
console.log(triple(square(6)));
console.log(uppercase(intense('This sentence is loud')));
Here is an example of composing a function with an existing composed function. In this case, I want to take add an additional step to formatting the sentence, prior to running the previous composedFunction on it. To implement this, I create another composed function, where composedFunction wraps a new function. For the new function, I create a function that uses replace() to replace a specified word with a new word. I then create the new composed function by passing in the existing composed function and the new replaceWord function to my new composed function, composedFunction2.
const composedFunction2 = (composedFunction, functC) =>
(sentence, original, newWord) => composedFunction(functC(sentence, original, newWord));
const replaceWord = (string, original, newWord) => string.replace(original, newWord);
const loudReplace = composedFunction2(loud, replaceWord);
console.log(loudReplace('This sentence is loud, so very very loud', 'loud', 'noisy')); //THIS SENTENCE IS NOISY, SO VERY VERY NOISY!!!!
The above composed of composed function could also be written in a single statement as seen below:
const composedFunction = (functA, functB, functC) =>
(...args) => functC(functB(functA(...args)));
const upperIntenseReplace = composedFunction(replaceWord, uppercase, intense);
console.log(upperIntenseReplace('This sentence is not quiet, not quiet at all', /quiet/g, 'silent'));
//THIS SENTENCE IS NOT SILENT, NOT SILENT AT ALL!!!!