Functional programming is all the rage in JavaScript these(last 2 years?) days and for all the good reasons. I like how functional programming concepts are simple to implement, yet powerful in JavaScript. Once such concept, I'd like to discuss is point-free
style.
What is point-free
?
The idea is simple enough. Instead of using arbitrary variable names for function parameters only to pass them to another function as arguments, you just skip them.
Example:
const isGreaterThan5 = x => x > 5
// instead of
let numbersGreaterThan5 = numbers.filter(number => isGreaterThan5(number))
// use point-free
let numbersGreaterThan5 = numbers.filter(isGreaterThan5)
"Point-free style — aims to reduce some of the visual clutter by removing unnecessary parameter-argument mapping." - Kyle Simpson
Point-free style is great. It is easy to adopt and really addictive once you get used to it. However, consider the following snippet
fetchUsers().catch(Raven.captureExeception)
Here Raven
is Sentry's NodeJs SDK. You would expect this to work, as the err
argument to the callback passed to catch will be passed to Raven.captureExeception
. However, if you would try that you'd get
Cannot read property 'generateEventId' of undefined
So what went wrong? this
.
If you remember your JavaScript correctly, you should know what to expect here:
const obj = {
log () {
console.log(this)
}
}
const x = obj.log
obj.log() // logs: obj { ... }
x() // logs: window (or the global in environment)
Same thing happens when you try to use point-free style. Since .captureExeception
is not called off Raven
object, this
inside .captureExeception
will not refer to Raven
, but the global and hence the error. You can check the source code here.
Lesson #1: Object-Oriented Programming and Functional programming don't mix well.
Before you jump at me for drawing such over generalized conclusions. This is just what I've found in my brief experience, yours may differ and if it is, I'd like to read about it.
Next, we have a naive use case
const numbersValues = ["1", "2", "3"]
const numbers = numbersValues.map(parseInt)
numbers // Outputs: [1, NaN, NaN]
You probably know what happened here. But since it is so easy and tempting to make such mistake, I'll explain. If you pull the documentation for .map
and parseInt
on MDN, you'll find the following syntax:
// Array.prototype.map
let new_array = arr.map(function callback( currentValue[, index[, array]]) {
// return element for new_array
}[, thisArg])
// Syntax for parseInt
parseInt(string [, radix])
You'll quickly realize what is happening here, the index
for each element is being passed to parseInt()
as the second argument causing the currentValue
to be parsed to int but in radix = index
. Since "3" in not a valid number radix 2
(binary), we get NaN.
Lesson #2: You must be certain of both the arity and signature(s) of concerned functions when using point-free.
That's it folks!
I'll be interested in reading through more such caveats, please share them!. I work with Node, Postgres, Ember and Vue. Hit me up if you want to discuss something interesting related to these.
Top comments (1)
It is really easy to miss those errors. Typescript surely helps a lot with the signature and arity mismatch issue, but if those are unintentionally same the caveat still exists.