1

Here's where it starts: This below snippet doesn't work as expected:

['1', '2', '3'].map(parseInt)           // [1, NaN, NaN] <- bad output

A simple change remedies it:

['1', '2', '3'].map(a => parseInt(a))   // [ 1, 2, 3 ] <- good output


But this book I'm reading has a different remedy:

Create a unary function, which takes in a function (parseInt in our case) and if the function has n arguments, it returns a new function that has only a single argument. Here's how the book does it:

const unary = (f) => {
  return f.length === 1 ? f : (args) => f(args)
}

['1', '2', '3'].map(unary(parseInt)) // [ 1, 2, 3 ] <- good output again!

My question is:

  1. How is this unary function is working?
  2. Any practical scenario where this function (or at least this idea in general) will find a good use?

Any help would be much appreciated :)


Note to the future reader: Checkout currying

Somjit
  • 2,503
  • 5
  • 33
  • 60
  • As it is not immediately clear whether `unary` refers to the unary numeral system used to express the numeric parseInt result or the arity of the callback function, I would advise against its use in practice. – le_m Mar 18 '17 at 13:10
  • @le_m [Unary functions](https://en.wikipedia.org/wiki/Unary_function) are a widely used concept in Functional Programming. It may be _slightly_ confusing that `parseInt` is being used for the example here, but I don't see any problem with that name. – JLRishe Mar 18 '17 at 13:19

3 Answers3

3

How is this unary function is working?

The length of a function is how many declared non-rest parameters it has prior to the first parameter with a default value*. So unary works by seeing if the function claims to accept just one parameter and if so returning the original function, and if not by creating a new function that only ever calls the original with one argument.

This check is not reliable and I wouldn't use that version of unary. Lots of functions change their behavior based on how many actual arguments they get, even if they only declare one.

Any practical scenario where this function (or at least this idea in general) will find a good use?

A version that always did the wrapper (without looking at length) could be handy for situations just like your parseInt one: You want to call the other function with only X arguments (1, in your case), regardless of how many are supplied to your callback.

This kind of thing is quite common in functional programming. If you're not doing functional programming in general, then probably in these days of arrow functions it wouldn't have much use. But a fair number of people use JavaScript in the functional way.


* Yes, it's that complicated. Examples (requires a browser with support for ES2015 [aka "ES6"] default parameter values and rest parameters):

function a(one) { }
function b(one, two) { }
function c(one, two, ...rest) {}
function d(one, two = 42, ...rest) {}
function e(one, two = 42, three) {}
console.log(a.length); // 1
console.log(b.length); // 2
console.log(c.length); // 2
console.log(d.length); // 1
console.log(e.length); // 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for a very detailed answer! `A version that always did the wrapper (without looking at length)` I'm finding this part to be a bit difficult to understand. Also, When we are talking about only one argument, which one is that? is `['1', '2', '3'].map(unary(parseInt))` not calling `parseInt` on each of the strings at a time? If so, then isn't `parseInt` _is_ getting just one argument? I can't figure out exactly where the multiple argument thing is happening here! – Somjit Mar 18 '17 at 13:10
  • 1
    @Somjit: To work reliably, `unary` would want to be: `const unary = f => arg => f(arg);` -- that is, not look at `f.length`, since it cannot rely on what `f.length` tells it. – T.J. Crowder Mar 18 '17 at 13:16
  • 1
    @Somjit: The problem with `['1', '2', '3'].map(unary(parseInt))` is that every time `map` calls its callback, it passes **three** arguments: The entry, its index, and the array. `parseInt` uses its second argument if provided: It's the radix (number base) it should use for parsing. So on the first call, `map` does `parseInt('1', 0, theArray)` which is okay because `parseInt` ignores that 0; but on the second callback, `map` does `parseInt('2', 1, theArray)`, which tells `parseInt` to use number base 1, which makes no sense. – T.J. Crowder Mar 18 '17 at 13:18
  • 1
    @Somjit: `unary` fixes this by returning a new function that will only call `parseInt` with the *first* argument it receives. So `map` calls `f('1', 0, theArray)` and `f` calls `parseInt('1')`; `map` calls `f('2', 1, theArray)` and `f` calls `parseInt('2')`, etc. – T.J. Crowder Mar 18 '17 at 13:18
  • 1
    Finally, I understand! Thank you so much! – Somjit Mar 18 '17 at 13:21
  • 1
    @Somjit: If the actual problem was that you didn't understand *why* `unary` was needed, then in seems you put the cart before the horse in asking *how* it works. There are many duplicate [questions](http://stackoverflow.com/questions/262427/javascript-arraymap-and-parseint) that explain the problem. –  Mar 18 '17 at 13:44
  • 1
    @squint: Yeah. :-) I read the question and thought "Okay, he knows why `map(parseInt)` does that and is curious about this `unary` thing..." – T.J. Crowder Mar 18 '17 at 13:46
  • @squint Well yes :) But since my keywords didn't return any thing useful, and definitely not the question to which you linked, I thought it would be 'more complete' to ask how _and_ why. Especially when I can ask someone who really knows his thing :) – Somjit Mar 18 '17 at 13:56
  • 1
    @Somjit: Fair enough. Just wanted to mention that since most of the answerers seemed to arrive at the same conclusion. Either way, glad you got it figured out. –  Mar 18 '17 at 14:05
3

in this unary function, fn.length is checking the arguments length of parseInt, if the parseInt have only one arguments then it return the function as usual. but in this scenario, we have got 2 arguments in parseInt which is parseInt(value ,radix) so our fn.length === 1 failed. if it have 2 arguments then it return the parseInt with one argument which is value only. eg parseInt(value) not parseInt(value, radix)

2

How is this unary function is working?

The length property on a function returns the number of formal parameters that a function has*. The unary function checks whether the function has one formal argument. If so, it just returns the original function. If not, it returns a new function that takes a single parameter, passes it to f, and returns the result.

Any practical scenario where this function (or at least this idea in general) will find a good use?

As shown in the example, there are certain functions that change their behavior depending on how many arguments are passed into it. Often (such as when you're using .map), you really just want to pass in one argument, and unary() helps with that.


* Or at least it used to. With the release of ES6, it's more complicated now. See T.J. Crowder's answer for details.

JLRishe
  • 99,490
  • 19
  • 131
  • 169