Here is an example showing how we can accomplish a lot using functional traits available in the JavaScript language. I first will show the problem, then will show a typical object-oriented solution, then will remove all the extra code using the functional approach.
Example
Let us start with a simple program: add two numbers and print the result
1 | function add(a, b) { |
Everything is working fine, and we decide to add logging to the add
function. Maybe we want
to run this function on the server, or help debugging in case there is an unexpected result.
We can add console.log
statement to the function itself
1 | function add(a, b) { |
This is fine, but it pollutes the standard output stream with lots of noise. We really only want to turn
the argument logging inside the add
function sometimes, leaving it off by default. We could pass a flag
into the function
1 | function add(a, b, verbose) { |
This is a bad solution
- Extra
if
statement increases the complexity of the function - Passing extra parameters to the function makes the function's signature a mess
add
function mixes addition and logging aspects
Object-oriented solution
Let us move the logging logic (verbosity) into a singleton object Log
. Any function that desires to print,
can just log the message via this singleton.
The Log
object can then decide if the logging is below the threshold and silently ignore it. Otherwise it will
print it.
1 | var Log = { |
There is nothing wrong with this approach. But I personally avoid it for several reasons.
It is verbose
The internal state (
debugMode
variable) is public and can change at any moment. For example, a rogue function can change it before printing and forget to restore it1
2
3
4
5
6
7
8function add(a, b) {
Log.debugMode = true;
Log.log(true, 'a', a, 'b', b);
return a + b;
}
Log.debugMode = false;
add(2, 3);
// debug messages are printed from now on!When passing
Log.log
method around we must be careful to bind it to theLog
instance.1
2
3
4
5
6
7
8
9
10var Log = {
...
debug: function () {
var args = Array.prototype.slice.call(arguments, 0);
args.shift(true);
this.log.apply(this, args);
}
};
[1, 2, 3].forEach(Log.debug);
// error - `this` inside Log.debug does not point to LogIn order to log something you need to read
Log
object's documentation to see how the state needs to be setup before thelog()
call.
Functional approach
Let us make a single function with first arguments being debug mode and message verbosity. The rest of the arguments are values we want to print to the console. This function thus has all the information to decide how to behave.
1 | function log(debugMode, verbose/*, and print arguments */) { |
Now we can create the print function to be used in the program by partially applying log
function.
This makes the debugMode
state variable essentially private.
1 | var print = log.bind(null, true); |
We can easily use print
function as iterator callbacks, even binding verbose
argument.
1 | [1, 2, 3].forEach(print.bind(null, false)); |
Not only the debugMode
inside the print
function is private to the function,
the print
reference itself is immutable.
If we want to switch the debugMode
from true
to false
we need to create a new print reference
1 | var print = log.bind(null, true); |
This feature is not as important in this example, but data immutability makes reasoning about the data flow easier, see immutable example.
Because we fix the internal state of the log
function left to right using built-in Function.prototype.bind
method, we need to pay careful attention to the argument order when designing the log
function. I advise
to put the arguments least likely to change first. Otherwise, you would need to use 3rd party library
to perform selective partial application.
We can make the logging function robust by defending it against invalid inputs. For example, using check-more-types/defend method
1 | var check = require('check-more-types'); |
Related: