Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern for treating zero as truthy

I often do stuff like this:

delay = delay || 24; // default delay of 24 hours

But I actually want to permit 0, and 0 || 24 === 24, instead of 0.

I'm wondering what the best pattern is to take user input from command line, or input from wherever, and do the same logic, only treat zero as truthy. I think the best pattern I've found is to do exactly that:

delay = (delay === 0 ? delay : (delay || 24));

Firstly, it permits things like 'abc', which is really wrong. But if I put in an early + it lets null slide through, which is also wrong. Secondly, very ugly, because it's clearly working around a language deficiency rather than doing something elegant with the language tools available. And not terribly readable. I'm doing something that is one line of thought and I'd like to do it in one actual line of code (not one line on technicality, like this is). But most other ideas I have had get even uglier:

delay = typeof delay === 'number' ? delay : 24; // but typeof NaN === 'number', so
delay = (!isNaN(delay) && typeof delay === 'number') ? delay : 24;

Note that this actually would work with string - if i were interested in accepting "", then

str = typeof str === 'string' ? str : 'default';

Since there is no NaN hole, and this is intelligently readable: if we have a string use that, otherwise use defaut.

Or this route:

delay = !isNaN(+delay) ? delay : 24; // fails on null
delay = !Number.isNaN(+delay) ? delay : 24; // still fails on null
// same thing with null check, now way uglier than we started

So I still like my hacky ternary and boolean logic better. Yes, I am looking for a condensed, one-line solution, since JS is rife with patterns and what would be clever in many other languages is well-recognized and readable and clear in JS. But I'm novice and trying to learn good patterns, hence, this question.

To be more explicit on the requirements:

  • 0 needs to go to 0.
  • undefined needs to go to 24.
  • All actual numbers under typeof need to go to themselves, except NaN.
  • I strongly feel null should go to 24 because I very rarely use JS code that treats null and undefined differently on purpose. I feel it's better to keep it that way.
  • I slightly feel NaN should go to 24 because this more closely follows the || pattern. Falsy things should go to default.
  • 'abc' should go to 24 - in my real application this is user input, and the user should not mistakenly type, say an email.
  • '123abc' should ideally go to 24, which conversion to Number catches but parseInt does not. I believe emails can start with numbers, so this drives the point home that this is something that ought to be caught.

Underscore or lodash answers are acceptable, in particular, to those of you who have lectured me on trying to be "clever" instead of writing a 2-3 line function. Those libraries exist precisely because there are many simple 2-3 line functions accomplishing the same thing in many places in many code bases all over the world, and it's far more readable and maintainable and robust to have those isolated as something like, say, _.readNumber. If no such method exists and I am able to come up with general enough requirements, I will submit a poll request myself and post that as answer to this question. This is something I like about JS - it has good ecosystem in that it's very possible to not have to write these utility methods. Since I'm particularly dealing with user input it may be better for me to write a slightly more specialized function and submit to commander.js, which is where I keep needing this.

like image 555
djechlin Avatar asked Jul 02 '13 19:07

djechlin


3 Answers

Nowhere is int a requirement mentioned, so assuming you want any number, otherwise defaulting to 24, you could use this:

delay = isFinite(delay) ? delay : 24;

Or the safer:

delay = isFinite(parseFloat(delay)) ? delay : 24;

Or the Robert Lemon special:

delay = isFinite(parseFloat(delay))|0||24;

Of course having someone else understand the statement at a glance is more important than syntactic sugar. You're writing code to be understood by man and machine, not to ward off industrial spies.

like image 119
Simon Sarris Avatar answered Nov 17 '22 22:11

Simon Sarris


The cleanest solution, by far:

delay = numberOrDefault(delay, 24);

// i = i || 24 doesn't work with i = 0, so this small func takes care of this.
function numberOrDefault(input, default) {
    // a few lines handling your case
}

Don't try to abuse the language. Don't try to be clever. Don't try to obfuscate your code. It will serve noone but your ego, and will hurt the maintainability and readability of your code.

Functions are there and can have names. They're done exactly for the purpose you're looking for: give names to some bunch of instructions. Use them.

like image 39
Florian Margaine Avatar answered Nov 17 '22 22:11

Florian Margaine


Assuming user input as some comments are saying, then it begins as any possible string so may as well test it.

delay = /^(\d+)$/.exec( delay ) ? Number( RegExp.$1 ) : 24;

Note this also protects against negative integers, which although not given as a requirement is nonsensical as a delay of time.

like image 1
Tim Avatar answered Nov 17 '22 20:11

Tim