By Francis Murillo
On Apr. 14, 2016
At Manila, JavaScript #06
https://github.com/FrancisMurillo/the-power-of-functions-in-javascript
- You know the basics of JavaScript syntax
- I am talking about JavaScript in the browser
- You and I hate presentations that waste time
Just a mediocre software developer.
Judge a man by his questions rather than his answers
– Voltaire
Who here thinks JavaScript (in the browser) sucks?
Where do I begin?
- Lousy type system
- No module system
- Global by default
- No block scope
- Monkey patching
- The rest couldn’t fit on one slide
But I am not here to bash on the language.
How do we write good code in a (sucky but awesome) language?
How do write code that is…
- Modular
- Extensible
- Reusable
- Readable
- Poetry
JavaScript already has a concept for that
It’s not a framework, or a library or a new concept and it’s been around since ancient times
The building blocks of programs
var theAllPowerful = function () {
return 'Bow before me';
}
The best thing about JavaScript is its implementation of functions. It got almost everything right. But, as you should expect with JavaScript, it didn’t get everything right.
– Douglas Crockford
In order to understand their supremacy, let’s review what functions look like in JavaScript first.
Just a brief review of the following.
- Functions
arguments
keywordcall
andapply
function- Closure / Function Scope
The bread and butter of programmers
There are two primary ways to declare a function.
// Function statement
function greetLucy() {
return 'Hello Lucile';
}
// Function expression
var greetPatty = function _greetPatty() {
return 'Hello Sir';
};
There’s three known ways to call or invoke a function.
// Math yo
var add = function _doTheMath(x, y) {
return x + y;
};
// The canonical function invokation
add(1, 2);
// The functional way of calling functions
// We'll get to this later
add.call(null, 1, 2);
// Same thing above but the arguments is passed as an array
add.apply(null, [1, 2]);
return
is the output of the function. If there is no return
, an
implicit undefined
is the output.
function returnSomething(x) {
return 'I call ' + x.toString();
};
var noReturn = function () {
// do something
}
console.log(returnSomething('Pikachu')); // out: 'I call Pikachu'
console.log(noReturn()); // out: undefined
For this presentation, the preferred way of declaring a function is a function expression to avoid function hoisting.
var theLongerWay = function isTheSaferWay() {
return true;
};
Every function has an implicit variable arguments
var MyModule = {
whatIsThis: function (/* args */) {
// And what is
console.log(arguments);
}
}
MyModule.whatIsThis(1, 2, 3, 4);
// out(arguments): [1, 2, 3, 4]
An array-like object containing the arguments
var showMeTheEvidence = function (a, b, c) {
var args = arguments;
console.log([a, b, c]);
console.log(args);
}
showMeTheEvidence(1, 2, 3) // out: [1,2,3]
showMeTheEvidence('This', 'is', 'nuts') // out: ['This', 'is', 'nuts']
Whenever I depend on the arguments
keyword, I put a comment on the
function header what the local variable name would be and convert it
into an array using Array.prototype.slice.call(arguments)
or the
shorter [].slice.call(arguments)
.
var extraRicePlease = function (/* args */) {
var args = [].slice.call(arguments); // This is awkward
args.push('Extra rice'); // Adding arguments
console.log(args);
};
extraRicePlease('BBQ', 'Ice Tea'); // out: ['BBQ', 'Ice Tea', 'Extra Rice']
Every function has the method call
and apply
to invoke them functionally
var fullName = function (firstName, lastName) {
// return [firstName, lastName].join(' ');
return firstName + ' ' + lastName;
};
// Normal invokation
console.log(fullName('Francis', 'Murillo')); // out: Francis Murillo
// Using call() without the
console.log(fullName.call(null, 'Mr', 'Robot')); // out: 'Mr Robot'
// Using apply()
console.log(fullName.apply(null, ['Jake', 'Dog'])); // out: 'Jake Dog'
call
and apply
will always have null
as the first argument
since we don’t care about this
.
apply
is the more preferred way of calling functions if you don’t
know the arguments, and call
if you do.
Inner functions have access to it’s the variables of the outer function
var outerFunction = function (outerArg) {
var outerVar = outerArg + 10;
return function innerFunction(innerArg) {
var innerVar = outerVar + innerArg;
return [outerArg, outerVar, innerArg, innerVar];
}
}
var newFunction = outerFunction(10);
// outerArg: 10
// outerVar: 20
console.log(newFunction(20));
// out: [10, 20, 20, 40]
console.log(newFunction(100));
// out: [10, 20, 100, 120];
Good design is not about making grand plans, but about taking things apart.
– Rich Hickey
At last, let’s talk about functions.
I don’t want to bore you with a definition. It’s really just a mindset
- Input and Output
- Combining Functions
- Process over Step
- Everything is a Function
- Avoiding State
- First principles
- Easier to write good code
- Easier to reason
- Natural and Mathematical
- Fun and Creative
- A full course in this paradigm
- A pure functional style
- An introduction to JavaScript
- Easy
To introduce you to think and write in a functional style can help you write good code in JavaScript and nothing more.
What is the most resilient parasite? Bacteria? A virus? An intestinal worm? An idea. Resilient… highly contagious. Once an idea has taken hold of the brain it’s almost impossible to eradicate. An idea that is fully formed - fully understood - that sticks; right in there somewhere.
– Cobb from Inception
The whole of the presentation
- Thinking in terms of collections or as a whole
- Separating behaviors and combining them
map
,filter
andreduce
compose
andcurry
You do not have to understand the code, just feel and see that this is much cleaner and better as a whole.
The idea is more important, ignore the nuances of the language but see that it is easily done.
Let’s talk about lists and their functions namely
Spoilers
.forEach
.map()
.filter
.reduce
Who here uses a for loop like so?
var items = ['Hey Arnold', 'Adventure Time', 'JavaScript: The Good Parts'];
for (var i = 0; i < items.length; i += 1) {
console.log(item[i]);
}
Intention
When iterating through an list, one should not be concerned with the length and index of it. All you need is the element and do something with it.
var heroes = [ 'Cloud', 'Tyler', 'Joel'];
for (var i = 0; i < heroes.length; i+= 1) {
var hero = heroes[i];
if (hero === 'Tyler') { // The intent
console.log('Everything is free of charge.');
}
}
How do we loop over each element without being concerned with the loop mechanics?
Our journey starts by using the forEach method of an list which allows you to loop through a list given a callback.
var assassins = [ 'Altair', 'Ezio', 'Connor'];
// Use forEach to iterate thorugh a list
assassins.forEach(function getConnor(assassin) { // Give the function a descriptive name
if (assassin === 'Connor') {
console.log('Where is Charles Lee');
}
});
Now this looks better, we separated the intention and the looping mechanism is now hidden as an abstraction.
But did you notice we passed a function to another function(the callback).
The bare minimum that allows JavaScript to be functional is that functions are first class objects meaning you can pass functions as arguments and as variables.
var myHandler = function (x) { // Assigning functions to variables
return 'You shall not pass';
};
var executeHandler = function (handler) { // A function taking a function
handler('An ignored value');
}
executeHandler(myHandler); // Passing functions around
If JavaScript did not have this feature, we would not be talking about it.
Let’s implement our own forEach
for analysis
// Naive implentation
var forEach = function (f, xs) {
for (var i = 0; i < xs.length; i+=1) {
var x = xs[i];
f(x);
}
};
var pi = [3, 1, 4, 1, 6, 1, 8];
// Almost looks the same
forEach(function displayTheNumber(n) {
console.log(n);
}, xs);
Pretty easy but this demonstrates how passing functions can improve the logic
Let’s move on a similar case of transforming a collection.
How about given a list of text, capitalize each one.
var words = ['What', 'Are', 'Words']; // Base list
var upperWords = []; // New list which is just a placeholder
words.forEach(function capitalize(word) {
var upperWord = word.toUpperCase();
upperWords.push(upperWord); // Add the value to the list
});
console.log(upperWords); // out: ['WHAT', 'ARE', 'WORDS']
In this case, we want to capitalize the whole list, not capitalize each word in the list.
The intention of the problem if we define it as a function.
var capitalize = function theLoopIntention(text) {
return text.toUpperCase(); // Notice our function has a return
};
What if we can just apply it as a whole instead of defining it?
Thankfully the map method of lists allows this.
var words = ['What', 'Are', 'Words']; // Base list
var capitalize = function theLoopIntention(text) {
return text.toUpperCase();
};
// Just like forEach
var newWords = words.map(capitalize);
// or if you want to inline it
var newWordsInline = words.map(function _inlineCapitalize(word) {
return text.toUpperCase();
});
console.log(newWords); // Like the last
Again, we have cleanly separated the intention from the loop.
Just some examples to get improve comprehension
var people = [
{ firstName: 'Linus', lastName: 'Van Pelt', nickname: 'Sweet Baboo'},
{ firstName: 'Charlie', lastName: 'Brown', nickname: 'Blockhead' }
];
var getNickname = function (person) {
return person.nickname;
};
var getFullName = function (person) {
return person.firstName + ' ' + person.lastName;
};
var capitalize = function theLoopIntention(text) {
return text.toUpperCase();
};
// Case: Getting a property from a list of objects
console.log(people.map(getNickname)); // out: ['Sweet Baboo', 'Blockhead']
console.log(people // You can chain maps by the way
.map(getFullName)
.map(capitalize)); // out: ['LINUS VAN PELT', 'CHARLIE BROWN']
Just like with forEach
, implenting the map
is easy.
var map = function (f, xs) {
var ys = [];
xs.forEach(function (x) {
var y = f(x);
ys.push(y);
});
return ys;
};
var wrapAsText = function (x) {
return '<p>' + x + '<p/>';
};
var labels = ['First Name', 'Last Name'];
console.log(map(wrapAsText, labels));
// out: ['<p>First Name</p>', '<p>Last Name</p>'];
Again we separated the intention from the implementation. Isn’t this a recurring theme?
The user of the function should only be concerned with the input and output, not how it is done. It is transforming the inputs to produce the output.
In the case of map
, given a list and a transformation function,
return a transformed list. You should not care how it did it, be it a
for loop or a forEach function or a recursion.
What, not how is the thinking style here.
So far we just separated the intent from our for loop and we came up with a neat little behavior.
Cool, so what’s next?
Let’s move on to filtering collections given a criteria.
Say we have a list of movie titles and we only want the James Bond films.
var films = {
{ name: 'Moonraker', category: 'James Bond'},
{ name: 'Sucker Punch' category: 'Action'},
{ name: 'Casino Royale', category: 'James Bond'}
};
var isJamesBondFilm = function _predicateIntention(film) {
return film.category === 'James Bond';
};
whatFunctionIsThis(isJamesBondFilm, films);
There is a function for lists called filter that can get our job done
Before we use filter
, a bit of terminology.
A predicate function is just a function that returns true
or
false
// Predicate functions
var equal = function (a, b) { return a === b; },
isOdd = function (n) { return n % 2 === 0; },
alwaysTrue = function () { return true; }; // Always returns true
// Not predicate functions
var toString = function (x) { return x.toString(); }, // Text value
concat = function (xs, ys) { return xs.concat(ys); }; // List value
Given a predicate or a boolean function and a list, return a new list where the predicate is true over the list.
// Some trivial examples
var numbers = [1, 2, 3, 4, 5];
var isOdd = function (number) {
return number % 2 === 1;
};
var isTheOne = function _heyJetLi(number) {
return number === 1;
};
console.log(numbers.filter(isOdd)); // out: [1,3,5]
console.log(numbers.filter(isTheOne)); // out: [1]
So we have another way to work with lists.
Again, we implement this for our own benefit.
// Another naive
var filter = function (p, xs) {
var ys = [];
forEach(function _checkPredicate(x) {
var y = x; // Just to get the name right with ys
if (p(x)) {
ys.push(y);
}
});
return ys;
};
var isLowerCase = function (text) {
return text.isLowerCase() === text;
};
var vendors = ['TORGUE', 'Hyperion', 'dahl'];
console.log(filter(isLowerCase, vendors)); // out: ['dahl']
Who here likes nested if
statements?
var a === true,
b === false;
if (!a) {
if (b) {
// Two ifs and you might want to refactor this if you can
} else {
}
}
var c === false;
if (!c) {
if (!b) {
if (!a) {
// Who the hell will write this?
}
}
}
I don’t like branching, it’s too easy to do and easy to make the code more complicated to reason about with too many ifs.
Consider this code as an example of nesting ifs. Given a list of books that are to be processed and a book isbn table, find all the unprocessed books without an isbn
var books = [
{ name: 'The Picture Of Dorian Gray', processed: false},
{ name: 'Minecraft For Dummies', processed: true},
{ name: 'Functional Programming In Javascript', processed: false}
];
var bookIsbnTable = {
'The Picture Of Dorian Gray': '1234',
'Minecraft For Dummies': '2344',
'Skyrim: The Official Guide': '3450'
}
var booksWithoutIsbn = [];
books.forEach(function (book) {
if (!book.isProcessed) {
var isbn = get(book.name, bookIsbnTable);
if (!isbn) {
booksWithoutIsbn.push(book);
}
}
})
Let’s refactor this to get to my point
Separate the main intentions
var books = [ /* ... */];
var bookIsbnTable = { /* ... */ };
// Intentions
var isProcessed = function (book) {
return book.isProcessed === true;
};
var hasNoIsbn = function (book) {
var isbn = get(book.name, bookIsbnTable);
return !isbn;
}
var booksWithoutIsbn = [];
books.forEach(function (book) {
if (isProcessed(book)) {
if (hasIsbn(book)) {
booksWithoutIsbn.push(book);
}
}
})
Let’s remove the first if
statement using the new found filter
power
var books = [ /* ... */];
var bookIsbnTable = { /* ... */ };
// Intentions
var isProcessed = function (book) {
return book.isProcessed === true;
};
var hasNoIsbn = function (book) {
var isbn = get(book.name, bookIsbnTable);
return !isbn;
}
var booksWithoutIsbn = [];
books
.filter(isProcessed) // The if statement becomes a transformation
.forEach(function (book) {
if (hasNoIsbn(book)) { // Just one if left
booksWithoutIsbn.push(book);
}
});
We removed one if, can we remove the other?
We can chain the filter
to remove the other if
var books = [ /* ... */];
var bookIsbnTable = { /* ... */ };
// Intentions
var isProcessed = function (book) {
return book.isProcessed === true;
};
var hasNoIsbn = function (book) {
var isbn = get(book.name, bookIsbnTable);
return !isbn;
}
var booksWithoutIsbn = [];
books
.filter(isProcessed)
.filter(hasNoIsbn) // We not have a filter chain
.forEach(function (book) { // This is somewhat awkward to have
booksWithoutIsbn.push(book);
});
And maybe we can remove the awkward forEach
here
We just let the filter
chain be the result and we’re done
var books = [ /* ... */];
var bookIsbnTable = { /* ... */ };
// Intentions
var isProcessed = function (book) {
return book.isProcessed === true;
};
var hasNoIsbn = function (book) {
var isbn = get(book.name, bookIsbnTable);
return !isbn;
}
var booksWithoutIsbn = books
.filter(isProcessed)
.filter(hasNoIsbn);
Although contrived, we just eliminated the if statements with the
filter
function.
Notice how the code is much more readable and easier to understand without the explicit ifs?
var booksWithoutIsbn = books
.filter(isProcessed)
.filter(hasNoIsbn);
The other thing about this is that the if
is a process flow or a
transformation over the list, not as a branching logic.
Also notice that if someone wants to add another condition, they are more likely to add another filter to the chain and less likely to just hack the if condition.
So we gained a new capability to filter a list and it applies to objects as well. Again this stems from the fact that we are just refactoring.
This leads us to the next virtue
By thinking about the operations over data, we can abstract the behavior to other containers.
var f = function (x) {
// Do something with x
}
mapX(f, xs); // What is xs? What if xs is a Promise, an Observable
filterX(f, xs); // Sometimes we don't really care
Again the process matters more than the data. If there was another data type the same ideas can come into place.
Finally, we move on to collecting or aggregating all the values of an collection.
For example, given a list of numbers, return their sum
// You're probably getting the picture or getting bored
var numbers = [1, 2, 3, 4, 5];
var add = function (a, b) {
return a + b;
};
var sum = 0;
numbers.forEach(function (number) {
sum = add(sum, number);
});
console.log(sum);
We have the reduce function for this
reduce
takes a combining function and a list and returns the
combined values of the list.
var numbers = [1, 2, 3, 4];
var add = function _theRealIntent(a, b) {
return a + b
};
var sum = numbers.reduce(function _combiningFunction(acc, number) { // Explain this bro
// acc is the accumulated value
// number functions as the number in the list
return acc + number;
}, 0);
// var sum = numbers.reduce(add, 0); // Better, once you understand combining operations
console.log(sum); // out: 10
Let’s implement this like the other two
Once you implement it, the idea of combining function is easy. Again this is just the code above that is just refactored.
var reduce = function (oper, initialValue, xs) {
var currentValue = initialValue;
forEach(function combine(x) {
// Combine the currentValue and the next
currentValue = oper(currentValue, x);
});
return totalValue;
};
Let’s have the usual examples
Some basic examples of reduce
// I like Math
var numbers = [1, 2, 3, 4];
var multiply = function (x, y) {
return x * y;
};
console.log(numbers.reduce(multiply, 1)); // out: 1 * 1 * 2 * 3 * 4 = 24
console.log(numbers.reduce(multiply, 5)); // out: 5 * 1 * 2 * 3 * 4 = 120
How about some real examples?
Sometimes in a list, you want to find a specific value given a criteria or predicate.
We can implement this using filter
but we can also implement it
using reduce
.
var find = function (p, defaultValue, xs) {
return xs.reduce(function (prev, next) {
return p(next) === true ? next : prev;
}, defaultValue);
};
var findWithFilter = function (p, defaultValue, xs) {
var foundValues = xs.filter(p);
return foundValues.length > 0 ? foundValues[0] : defaultValue;
};
var isTheMeaningOfLife = function (number) {
return number === 42;
};
console.log(find(isTheMeaningOfLife, 0, [36, 42, 48])); // out: 42
console.log(find(isTheMeaningOfLife, 0, [37, 43, 49])); // out: 0, the default value
So we now have a tool that aggregates or combines list.
We now have a trio of tools and it came naturally due to a need to separate intention. Nothing complex has been done so far which is impressive what a simple function can do.
Given a list of people, find all the people that are not minors and compute the total salary.
var people [
{ name: 'Mr Midnight', age: 20, salary: 50},
{ name: 'Mr Muffin', age: 25, salary: 60},
{ name: 'Ms Pepper', age: 17, salary: 40}
];
var isNotAMinor = function (person) {
return person.age >= 18;
}
var getSalary = function (person) {
return person.salary;
}
var add = function (x, y) {
return x + y
}
console.log(people // Wow
.filter(isNotAMinor)
.map(getSalary)
.reduce(add)); // Nice to see the three working together
Just a quick review of everything we’ve done.
- Think in terms of input and output
- Separate the intention from the implementation
map
,filter
andreduce
should be your best friends- Process over data
Let’s get to the fun stuff.
Spoilers
compose
&pipe
curry
Functions represents behavior or what we have been calling intentions.
Primarily, a function should represent an idea and not be tangled with other details
Let’s say we have two buttons with their corresponding handlers.
var buttonHandler = function () {
console.log('This button does something awesome');
}
var otherButtonHandler = function () {
console.log('This button does something way better than the other one');
}
All is well
Now, what if we want to track send information to the server that a
button was clicked? Let’s say we have a sendClick
function that
sends data to the server.
var sendClick = function () { /* whatever */}
var buttonHandler = function () {
sendClick(); // Dirty
console.log('This button does something awesome');
}
var otherButtonHandler = function () {
sendClick(); // What the hell are you doing here?
console.log('This button does something way better than the other one');
}
But this implementation is dirty, copy pasting a method for each handler and it ruins the fact the handler should be concerned with sending logging data or what have you.
What we want is to separate the handler from the logging. What we want is to execute a function before the main one.
Let’s call warm up with this doBefore
function combiner, we will
finally use apply
to invoke the functions.
var doBefore = function (before, main) {
return function _executor(/* args */) { // A function that combines functions
var args = [].slice.call(arguments);
before.apply(null, args); // Execute the function before
return main.apply(null, args); // Execute the main function
}
}
Let’s use this in our example
Let’s see how it makes the code much better
var sendClick = function () { /* whatever */}
var buttonHandler = doBefore(sendClick, function () { // Wrapping the handlers
console.log('This button does something awesome');
}); // This functions returns the combined function
var otherButtonHandler = doBefore(sendClick, function () { // Ditto
console.log('This button does something way better than the other one');
});
Notice our handlers are just the same except they are now wrapped with the logging function. This is what it means to combine and separate behaviors.
How many times have you copy pasted a block of function to several places without consideration for it’s purpose or intent?
If we cleanly separate the intentions of our code and combine them properly, we can get easy to read and understand code.
What we just did is make an higher order function but that’s just definition.
Once you understand that you can join functions together, you can create a pipeline using functions.
Let’s start with an simple problem of uppercasing every text field in an object then converting into JSON.
var isText = function (text) {
return typeof text === 'string';
}
var objectToUpperCase = function (object) {
return mapObject(function (value) {
return isText(value) ? value.toUpperCase();
}, object);
};
var objectToJson = function (object) {
return JSON.parse(object);
};
var data = { name: 'Muffin', breed: 'Puspin', age: 4 };
var output = objectToJson(objectToUpperCase(data)); // The end result
Better if the two functions can be joined as one intention, objectSerialize
?
Just to demonstrate the extra parenthesis are not just dirty, but harder to read and maintain
// Imagine you have three weird functions
var x = function (v) {/* ... */},
y = function (v) {/* ... */},
z = function (v) {/* ... */};
var a = null;
// This is ugly
var b = x(y(z(a)));
var t = function (v) {
return x(y(z(v))); // Better hide the mess like a guy
};
// Better but kinda hacked
var c = t(a);
What we really care about is c
, not x
, y
, or z
. What we need
is something to tie the pipeline.
So the function we’re looking for compose
, given two functions it
will call the second one with the arguments and pass it’s output to
the first function and be the function of the return value.
Easier to explain with code. Remember we want to remove the ugly wrapping of parenthesis in function invokation.
var compose = function (outer, inner) {
return function (/* args */) {
var args = arguments;
var innerValue = inner.apply(null, args),
outerValue = outer(firstValue);
// or which looks natural // return outer(inner.apply(null, arguments));
return nextValue;
};
},
Let’s see this in our example above
Let’s join those two functions in with compose
var objectToUpperCase = function (object) {
// ...
};
var objectToJson = function (object) {
// ...
};
// Tying the two functions together, looks cleaner than before
var objectSerialize = compose(objectToJson, objectToUpperCase);
// Remember to read from right to left in order of execution
var data = {
// ...
};
console.log(objectSerialize(data)); // Ah, looks cleaner.
We successfully removed those wonky parenthesis but how else can compose help us in our journey of better code?
Let’s get acquainted with our new friend, make it your BFF if you can.
// Mathematical Examples
var square = function (n) { return n * n; },
increment = function (n) { return n + 1; },
decrement = function (n) { return n - 1; },
display = function (n) { console.log(n); return n; };
var nextSquare = compose(square, increment),
incrementTwice = compose(increment, increment),
addZero = compose(decrement, increment),
displaySquare = compose(display, square);
Composing is better when you start using it with currying. But for now, how do we compose more than two functions?
So how do we compose functions all the way to the moon?
// Three functions
var escapeHtml = function (text) { /* ... */ },
wrapAsText = function (text) { /* ... */ },
wrapInDiv = function (text) { /* ... */ };
var data = ['5 Centimeters Per Second', 'Garden Of Words'];
// Plural compose, composes.
var htmlizer = composes( // Three compositions
wrapInDiv,
wrapAsText,
escapeHtml); // Do note, that this seems declarative on the process
// Hypothetical scenario in converting a list into Html text
console.log(data
.map(htmlizer)
.join('\n'));
First, we go back to our old friends
Let’s implement a few things to improve the readability of our grand
composes
// Returns an reversed version of the list
var reverse = function (xs) {
var nxs = xs.slice(); // Shortcut for copying an array
nxs.reverse(); // Mutates the array
return nxs;
};
// Returns the first element of the list
var first = function (xs) {
return get(0, xs); // or just xs[0] if you are in a hurry
}
// Likewise the rest of the lements of the list
var rest = function (xs) {
return xs.slice(1);
};
// Just a reduce with the initial value being the first value in the list
var reduceFirst = function (oper, xs) {
var initialValue = first(xs),
otherValues = rest(xs);
return reduce(oper, initialValue, otherValues);
};
I prefer to read left to right instead of right to left with my function pipeline.
var pipe = function (first, next) {
return compose(next, first);
};
var splitWords = function (sentence) { return text.split(''); },
splitParagraphs = function (doc) { return text.split('\n'); };
// Originally
var getWords = compose(splitWords, splitParagraphs);
// Really, notice the sequence is read left to right
var getWords2 = pipe(splitParagraphs, splitWords);
This is just compose
with the arguments reversed which might be a
small thing but helps in readability, just my prefrence anyway.
With that out of the way, we simply use reduceRight
and pipe
in one master stroke
var composes = function (/* fs */) {
var fs = [].slice.call(arguments);
// You can use compose instead of pipe but you would have to reverse the arguments
return reduceFirst(pipe, reverse(fs));
};
It is just basically starting at the end and composing all the way
back. That’s how easy it is to implement composes
.
Just for the sake of symmetry.
var pipes = function (/* fs */) {
var fs = [].slice.call(arguments);
return reduceFirst(pipe, fs); // This is just without the reverse
}
Now let’s see how they fit together.
Let’s check it out.
var twice = function (n) { return n * 2; },
half = function (n) { return n / 2; },
increment = function (n) { return n + 1; };
var sequence = composes(half, twice, increment);
var sequence2 = pipes(increment, twice, half, twice);
var sequence3 = pipes( // Preferred way of writing
increment,
twice,
half,
twice
);
Viola, we can compose as much as we want. But where does this leave us?
It’s not the fact that you use compose
or pipe
, but rather that
you want your code to be a single and straight process.
For example, displaying data in html. The process you want is.
- Source
- Map
- Render
But we tend to diverge from this as we add more features. The point is you want to maintain the integrity of the process.
Let’s have another case review. Given a list of employees and there salary, let’s compute their take home pay.
var codeCats = [
{ name: 'Mr. Muffin', salary: 100, gender: 'male'},
{ name: 'Ms. Fuzbudget', salary: 50, gender: 'female'}
];
var payComputer = function (codeCat) {
var pay = codeCat.salary / 2;
return set('pay', pay, codeCat);
};
console.log(codeCats.map(payComputer));
All is fuzzy and warm
Now what if every female employee gets paid 50% more due to a goverment law. What do you do to make it right?
var codeCats = [
{ name: 'Mr. Muffin', salary: 100, gender: 'male'},
{ name: 'Ms. Fuzbudget', salary: 50, gender: 'female'}
];
var payComputer = function (codeCat) {
var pay = codeCat.salary / 2;
var newerPay = codeCat.gender === 'female' ? pay * 1.5 : pay;
return set('pay', newerPay, codeCat);
};
console.log(codeCats.map(payComputer));
Not a problem, but what if you can’t modify payComputer
because
it’s a third party shiznit? Or what if there is another law, are we
going to add another if?
You know where this is going.
Let’s use composition to make the code cleaner.
var codeCats = [
{ name: 'Mr. Muffin', salary: 100, gender: 'male'},
{ name: 'Ms. Fuzbudget', salary: 50, gender: 'female'}
];
var femalePayRaise = function (codeCat) {
var basePay = codeCat.pay; // Must already be paid
return set('pay', codeCat.gender === 'female' ? basePay * 1.5 : basePay, codeCat);
};
var payComputer = compose( // Process is maintained
femalePayRaise,
function _originalComputer(codeCat) {
var pay = codeCat.salary / 2;
return set('pay', newerpay, codeCat);
});
console.log(codeCats.map(payComputer)); // Still well
We retain the original intent and managed complexity.
Although the example is quite contrived, the main point is to avoid complexity and maintain the simplicity of the process.
The thinking style should be a composition pipeline. A straight flow is better than a branching sewer.
As a tip, whenever there is a new functionality or rule, consider composition or the like to manage the complexity.
So we just implemented one of the cornerstones of functional programming and it wasn’t complicated. All we wanted was to remove the dirty parenthesis, amazing.
Let’s go learn the complimentary technique for composition.
Currying is a nice functional idea to sweeten composition
Consider this multiplication function, what if we want the first argument to be preset.
var multiply = function (x, y) { return x * y; }
// What if we want have the following?
var multiplyByTwo = function (y) { return multiply(2, y); },
multiplyByFive = function (y) { return multiply(5, y); };
// There is a sort of duplication here. How about?
var multiplyPreset = function (x) { // Neat little trick
return function (y) { // Return a function where the first argument is preset
return multiply(x, y); // Execute the actual function with the arguments
};
};
// Same as above but notice we removed the noise in declaring the functions?
var multiplyWithTwo = multiplyPreset(2),
multiplyWithFive = multiplyPreset(5);
console.log(multiplyWithFive(4)); // out: 20
console.log(multiplyWithTwo(2)); // out: 4
Let’s have another example with a function that takes three arguments
// Consider an function that add triples
var addTriple = function (x, y, z) { return x + y + z; };
// Let's repeat the neat little trick
var addTriplePreset = function (x) {
return function (y) {
return function (z) { // Looks kinda coo
return addTriple(x, y, z);
};
};
};
var addTen = addTriplePreset(10);
console.log(addTen(4, 5)); // out: 19
var addTenAndFive = addTriplePreset(10)(5);
console.log(addTenAndFive(6)); // out: 21
But this seems to be a function behavior, not logic. Maybe we can separate this behavior.
var curry = function (f) { // Thanks Haskell Curry
/* ??? */
};
// Let's curry our get function
// get takes the key and the object
var getId = curry(get)('id'); // or function (object) { return get('id', object); }
var users = [
{ id: 1, username: 'Beethoven'},
{ id: 2, username: 'Mozart'},
{ id: 3, username: 'Ravel'}
];
console.log(users.map(getId)); // out: [1,2,3];
console.log(users.map(function _getId(user) { // Compare with this
return get('id', user);
}));
Let’s implement the easy version
Copy pasting our trick from before
var curry = function (f) {
return function (x) {
return function (y) {
return f.apply(null, [x, y]); // or f(x, y);
}
}
};
This is enough but what if you want to implement it for three arguments or four? You could copy paste the same definitions over and over.
But there is a better way.
This is the most fun and challenging to code but the main concept is to keep returning a function until enough arguments have been supplied and then invoke it.
var curry = function _recursiveCurry(f) {
// Get the number of arguments in a function
var numberOfArgs = f.length;
// An helper function to curry the original function
var currier = function _recursiveCurry(previousArgs) {
return function _curried(/* currentArgs */) {
var currentArgs = [].slice.call(arguments),
newArgs = previousArgs.concat(currentArgs);
// If there is enough args, invoke the function
if (newArgs.length >= numberOfArgs) {
return f.apply(null, arguments);
} else { // If there is not enough args yet, keep currying
return _recursiveCurry(newArgs);
}
};
};
return currier([]); // Start recursion with no arguments
};
This is the only thing that looks complicated, I swear.
Just a contrived use of the generic curry
var computeName = curry(function _fullName(firstName, lastName) {
return firstName + ' ' + lastName;
});
var doctorName = computeName('Doctor');
console.log(doctorName('Death')); // out: Doctor Death
console.log(doctorName('Who')); // out: Doctor Who
var johnName = computeName('John');
console.log(johnName('Marshton')); // out: John Marshton
console.log(johnName('Doe')); // out: John Doe
// Cute tricks with it, but don't do this
console.log(computeName('Linus')()('Van Pelt')); // out: Linus Van Pelt
console.log(computeName()()()('Linus', 'Van Pelt')); // out: Linus Van Pelt
But where does this fit in with compose
?
Curry a function until it has one argument left which you can pass through a composition or mapping pipeline.
var heroes = [
{ name: 'Yang Xiao Long', team: 'RWBY'},
{ name: 'Ruby Rose', team: 'RWBY' },
{ name: 'Pyrrha Nikos', team: 'JNPR'}
];
// Remember set takes three arguments: key, value, object
var setHealth = curry(set)('hp', 100),
setScore = curry(set)('score', 0);
console.log(heroes
.map(setHealth)
.map(setScore));
// or if you care about performance
var setStats = compose(setHealth, setScore); // or pipe(setScore, setHealth);
console.log(heroes.map(setStats));
// Did I declare a function??
Doesn’t it look nice? We just created new functions from old ones and given them more specific uses.
How about using it in conjuction with filter? Given a list of heroes, I want to filter the intelligence type heroes in the list or team.
var heroes = [
{ name: 'Pudge', type: 'STR', kills: 15},
{ name: 'Puck', type: 'INT', kills: 13},
{ name: 'Lich King', type: 'INT', kills: 9}
{ name: 'Razor', type: 'AGI', kills: 4},
{ name: 'Ogre Magi', type: 'STR', kills: 1}
];
var eq = curry(function _equals(a, b) {
return a === b;
});
var getType = curry(get)('type'),
isInt = eq('INT');
var isIntType = pipe(getType, isInt);
console.log(heroes.filter(isIntType));
// compare with, I leave it to you which is better
console.log(heroes.filter(function _isIntType(hero) {
return heroes.type === 'INT';
}));
Still cool
Another cornerstone has been implemented. We now have the ability to create functions from old one.
But the real question is: are there more tricks like this?
With higher ordered concepts like composition and currying, if everything is a function then all of them benefit from higher order abstractions.
There are other higher order functions that sweeten the deal.
Now you know why get
and set
are functions so that they can be
curried and composed.
Tying this back to our old previous tools
var characters = [
{ id: 314, name: 'Dilbert', source: 'Dilbert'},
{ id: 319, name: 'Catbert', source: 'Dilbert'},
{ id: 325, name: 'Sally', source: 'Peanuts'}
];
var getId = curry(get)('id'),
getIds = curry(map)(getId); // Currying our implementation
console.log(getIds(characters)); // out: [314, 319, 325]
var isFromPeanuts = curry(get)('id'),
fromPeanuts = curry(filter)(isFromPeanuts);
console.log(fromPeanuts(characters)); // out: [{id:325,name:'Sally',source:'Peanuts'}]
var getIdByPeanuts = pipe(isFromPeanuts, getIds);
console.log(getIdByPeanuts(characters)); // out: [325]
If this did not impress you, I don’t know what will.
We implemented one of the nice tricks of functional programming, we can now extract functions from old one by presetting the arguments.
But the idea is that combining functions is really not that hard. How many more ways can we play with functions?
So those are the fundamental tricks.
compose
,pipe
andcurry
are fun tools to implement and have- Gluing functions are easy
- Making new functions from old
Did the code look cleaner with these concepts?
That is where it matters.
End of the line. Wrap it up fool. I hope you found
JavaScript is really cool for me because of the following.
- First class functions
- Closures
- Function scoped
That’s really it.
(I could say the same thing about Python which holds a special place in my heart.)
You could have noticed that all the machinery we built can be written by hand, a simple function and a desire to make code cleaner.
There are a lot of other things you can do with functions, really a lot more I didn’t mention or rather you can discover like uncharted territory.
Use your imagination.
If you forgot what I said, here it is.
- Use
map
,filter
, andreduce
- Learn
compose
andcurry
- Think input and output, processes not details
- Avoid for loops and ifs
- Hide implementation details
- Have fun
Compare JavaScript with Haskell if you want to see the light
- Partial Application
- Closures The chapter I removed from this presentation
- Types
- Monads
- Functors
- Patterns
- So much
I am not an prophet but…
- Functional Reactive Programming
- React & React Native
- Clojure / ClojureScript
- RxJs
- Elm
- Java 8: Lambdas
Really, it’s just a good time to learn a new paradigm to widen your perspective.
No matter what I say or what paradigm is out there. Good code is something you work for. There is really no silver bullet.
Don’t be a zealot. OOP and FP have their places in design and coding. Use your judgment which works for the correct situation.
But really in JavaScript, I find FP to be really easy to work with than its Prototypical nature.
Thank you for letting me waste your time. I hope you learned something or even just the idea.
- lodash - a good functional library
- jquery - not obvious but it really is a monad
- rxjs - functional reactive programming library
- haskell - the language drug to functional programming
- clojure - the functional lisp since Scheme
- clojurescript - the lisp for javascript
- elm - the functional javascript