Imagine you have a function, and it grabs the keywords from a typical
NodeJS package definition file package.json
. If you want, you can make a module
like this using npm init -y
command.
1 | { |
We can implement this function like this - the simplest way possible
1 | function grabKeywords(filename) { |
We can confirm this logic is working by writing a few unit tests
1 | describe('grab keywords', () => { |
The file test.json
is very simple
1 | { |
Notice I am using bits of ES6 in my code, which is ok.
What happens if I want to extend this code and return a default value if the file
does not exist, or does not have keywords
property? Let us leave aside non-existent files
- it probably is a runtime exception, and handle the case when the returned object does not
have the
keywords
property. We can handle this case every time a call to `grabKeywords1 is made
1 | var keywords = grabKeywords('./test.json'); |
Ok, but this means every call probably will have to add this piece of code, including the
if
branch, making it more complex.
If we push the default value check into the function itself, we can avoid the code duplication, giving the user a new feature at very little cost.
At first, we could have the default value right inside the function
1 | function grabKeywords(filename) { |
But this is very hard-coded. What if the users consider []
to be a wrong default value?
One is tempted to extend the grabKeywords
function with second parameter -
the default value the user can optionally receive.
1 | function grabKeywords(filename, defaultValue) { |
We are passing the default value (data) into the function and we had to add some more logic
(behavior === code) to the function itself: we had to add the logical OR operator
|| defaultValue
.
Notice that the implementation || defaultValue
is very subtly different from the
typeof keywords === 'undefined'
logic, or even what we probably meant to do.
The || defaultValue
will return the default value in many cases, like 0 || defaultValue
or "" || defaultValue
. Remember, the logical OR is the equivalent to the casting ==
operator.
In JavaScript, checking if an object does not have a property should be hasOwnProperty
method.
1 | function grabKeywords(filename, defaultValue) { |
Here is where things become very hairy. Some users would want the above hasOwnProperty
check before returning the default value. Others might want the simple || defaultValue
check because they might consider null
, 0
or ""
values to be non-existent.
So what should we implement inside the grabKeywords
function? Should it be hasOwnProperty
or || defaultValue
or some other check?
Here is where the functional programming principle comes in handy. Instead of passing
the data defaultValue
into the grabKeywords
function and implementing "One True Behavior",
we can easily pass custom behavior (a function) instead. Here is one implementation
1 | function grabKeywords(filename, defaultValue) { |
Notice that instead of passing call-time value, we passed a function that can intelligently decide what the returned value should be. The decision could be very simple: an identity function works for most cases that needed the initial logic and nothing else.
1 | function I(x) { return x; } |
For other cases, we could pass client side function to return a default value a client decides at the very last moment.
1 | function default42(value) { |
Boom! The library's author does not have to decide for everyone for all the eternity what the value comparison logic should be. Instead a provided function will be called, and it can make the decision in whatever manner the client needs. For example, one can implement a logic to assume that 3 keywors is NOT enough.
1 | function needAtLeast4(value) { |
FP vs OOP
A quick note why passing a function around is better than some software pattern
from the "Gang of Four" book. A single function is a very simple entity.
It has only 1 possible action - calling it.
No need to initialize an object in some specific way, or create a callback object,
etc. In most cases one can even see what a function expects by calling fn.toString()
and reading the source code, especially its signature line.
This is it. The functional programming is all about passing behavior (functions) around, rather than hardcoding it. One can argue with the implementation in this example, but it is simple to demo the point, not be an ideal code.