DEV Community

Glad Chinda
Glad Chinda

Posted on • Updated on

The Single Argument Problem: Hello Unary!

This is my first 2020 post on the DEV community and I have decided to share something really simple and possibly cool (depending on what you think of it). However, I do hope that you find it insightful. 😜


In this post, I will explain, in less than 5 minutes, a problem I prefer to call the Single Argument Problem and how it manifests itself. Finally, I will share how it can be fixed, with special emphasis on the unary() helper function.

It is important at this point to note that all code snippets in this post are written in JavaScript. However, the concepts described in it may apply to several other programming languages. LET'S JUMP RIGHT IN πŸš€πŸš€

Background

You are building a JavaScript application that requires an array of integers. However, there is no guarantee that you will always have an array of integers. So you decide to convert each element of every array to an integer before working with the array.

Based on your decision, you came up with this simple function:

function getIntegerArray (arr) {
  return arr.map(parseInt);
}

The function effectively maps every element of the original array to a new array using the global parseInt() as the mapping function.

The parseInt() function is guaranteed to return an integer for a string that can be parsed into an integer, and NaN otherwise. You can learn more about how parseInt() works here.

Then comes the moment of truth. You tested the getIntegerArray() function with an array like so:

console.log(getIntegerArray(['1', '3', '5', '7', '9']));
// [1, NaN, NaN, NaN, NaN]

You were expecting to get this:
[1, 3, 5, 7, 9]

but instead you got:
[1, NaN, NaN, NaN, NaN]

What could you have gotten wrong? You'll find out as you read along.

The Single Argument Problem

The mapping function passed to Array.prototype.map() has the following call signature:

function mapFunction (element, index, arr, thisArg) { ... }

Hence, any function passed to Array.prototype.map() will be called with 4 arguments for every element in the target array.

In the case of your getIntegerArray() function, the mapping function is the global parseInt() function. Here, is kinda a breakdown of the function calls for each element of the array:

parseInt('1', 0, ['1', '3', '5', '7', '9'], this) // 1st element
parseInt('3', 1, ['1', '3', '5', '7', '9'], this) // 2nd element
parseInt('5', 2, ['1', '3', '5', '7', '9'], this) // 3rd element
parseInt('7', 3, ['1', '3', '5', '7', '9'], this) // 4th element
parseInt('9', 4, ['1', '3', '5', '7', '9'], this) // 5th element

Since the global parseInt() function can only take 2 arguments, it discards the extraneous arguments like so:

parseInt('1', 0) // 1st element
parseInt('3', 1) // 2nd element
parseInt('5', 2) // 3rd element
parseInt('7', 3) // 4th element
parseInt('9', 4) // 5th element

The second argument passed to the parseInt() function is taken to be the fromRadix of the string as first argument to be parsed.

So if the second argument to parseInt() is 0, it is treated as though it was not passed; hence JavaScript determines the correct radix before parsing. You can learn more about how parseInt() works here.

If you execute the functions from before for each element of the array, here is what you'll get:

parseInt('1', 0) // 1
parseInt('3', 1) // NaN
parseInt('5', 2) // NaN
parseInt('7', 3) // NaN
parseInt('9', 4) // NaN

which explains the result you got earlier. It appears that for your getIntegerArray() function to work as intended, the parseInt() mapping function will have to be called with only the first argument (effectively discarding all the other arguments).

This is what I will refer to as the Single Argument Problem.

Fixing the Problem

Now that the problem have been identified, fixing it is only a matter of making some adjustments to the initial function to ensure that the parseInt() mapping function is called with only the first argument.

Here is the modified getIntegerArray() function:

function getIntegerArray (arr) {
  return arr.map(x => parseInt(x));
}

A very simple modification and, Eureka!!! you fixed it πŸ‘πŸΌ. In fact, the arrow function syntax makes the modification more subtle in appearance that you might not even notice it.

Now you tested the getIntegerArray() function with the same array again and got the correct result you expected, like so:

console.log(getIntegerArray(['1', '3', '5', '7', '9']));
// [1, 3, 5, 7, 9]

Introducing the unary() helper function

The Single Argument Problem as described earlier is a very popular problem you might face when using callback functions in your applications. As a result, JavaScript utility libraries like Lodash (Underscore) provide a higher-order _.unary() helper function that can be used to create a new function (that can be called with only one argument) from an initial function.

Using this unary() helper function, the getIntegerArray() function can be modified as follows:

function getIntegerArray (arr) {
  return arr.map(unary(parseInt));
}

Calling unary(parseInt) returns a new function that delegates its call to parseInt() but with only one argument. Hence, this effectively solves the Single Argument Problem for any use case.

Here is a simple implementation of the unary() function:

function unary (fn) {
  if (typeof fn === 'function') {
    return function _unaryFn (firstArg) {
      return fn(firstArg);
    }
  }
  return fn;
}

A more compact version of the unary() function looks like this:

function unary (fn) {
  return (typeof fn === 'function')
    ? firstArg => fn(firstArg)
    : fn;
}

Final Words

That is all I have to say. Thanks for your patience and effort to make it to the end of this post. I hope you found it insightful.

Please remember to:

  • Hit the Like Button
  • Share with a Friend
  • Follow me on Twitter (@gladchinda)

Hope to see you again soon.

HAPPY CODING

Top comments (0)