DEV Community

Abhishek Gupta
Abhishek Gupta

Posted on

Caveats around point-free functions

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)

Collapse
 
hereisnaman profile image
Naman Kumar

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.