5

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.

djechlin
  • 59,258
  • 35
  • 162
  • 290
  • Why not `delay = delay > -1 : delay : 24`? Are negative values accepted? – Karl-André Gagnon Jul 02 '13 at 19:21
  • @Karl-AndréGagnon in my case no, that's actually pretty reasonable. – djechlin Jul 02 '13 at 19:21
  • What do you mean by "NaN hole"? How is `num = typeof num === 'number' ? num : 24` insufficient? – John Dvorak Jul 02 '13 at 19:21
  • 1
    @Karl-AndréGagnon `null > -1 => true`, though. – Chris Heald Jul 02 '13 at 19:21
  • Ugh right, the problem with most early coercion solutions is `+null === 0`. – djechlin Jul 02 '13 at 19:22
  • First, you should be doing a `delay = parseInt(input, 10)`. Then you know that `delay` is always a `number`. This simplifies things to `delay = isNaN(delay) ? 24 : delay;` – Shmiddty Jul 02 '13 at 19:23
  • Do you need to reject `NaN`? – John Dvorak Jul 02 '13 at 19:23
  • Well, it would actually be `delay >= 0` since you can send -0.5 and work. And are you sure `null >= 0 = true`? – Karl-André Gagnon Jul 02 '13 at 19:23
  • @JanDvorak because `NaN` is a number as far as `typeof` is concerned. String doesn't have an analogous problem. – djechlin Jul 02 '13 at 19:24
  • @Karl-AndréGagnon per my Node REPL `null > -1` evaluates to `true` and I believe this is because first `null` is coerced to `Number` and therefore 0. – djechlin Jul 02 '13 at 19:24
  • @djechlin `num = num === num && typeof num === 'number' ? num : 24` – John Dvorak Jul 02 '13 at 19:25
  • @Shmiddty that accepts things like `123abc` which is a type of error I would like to catch, see http://stackoverflow.com/a/17349809/1339987 for what I've figured out regarding this situation outside of dealing with zero. – djechlin Jul 02 '13 at 19:26
  • You are right, `null >= 0 = true`, but `parseInt(null) >= 0 = false` : http://jsfiddle.net/KH2R3/ – Karl-André Gagnon Jul 02 '13 at 19:26
  • @Karl-AndréGagnon but `parseInt('123abc') >= 0` is true. – djechlin Jul 02 '13 at 19:26
  • So can we safely assume you are casting `delay` as a `Number`? `delay = Number(input);`? – Shmiddty Jul 02 '13 at 19:35
  • If you are not casting the input at all, `"0"` is already truthy. – Shmiddty Jul 02 '13 at 19:37
  • *"...it's clearly working around a language deficiency"* I'd be curious to know what specific language deficiency you're referring to. –  Jul 02 '13 at 19:39
  • @CrazyTrain the fact that `||` just makes sure you *have* a value 99% of the time and in patterns is used this way. This is the other 1%, when I really want to follow `typeof`'s logic more... but typeof accepts the falsy value `NaN`, hence this question being knotty. – djechlin Jul 02 '13 at 19:42
  • So you're talking about the fact that there are multiple "falsey" values in the language? –  Jul 02 '13 at 19:43
  • 1
    I avoid Crockford. What it comes down to is that JavaScript is loosely typed. That'll always mean a little more code when you need to be tighter, and vice versa. it seems that you want to use `||`, but you want `0` to be a truthy evaluation. But then you couldn't use `0` as a falsey value in other cases. The language has a way of dealing with situations where you need very specific behavior. It's called functions. –  Jul 02 '13 at 19:50
  • 1
    -1 because this is leading nowhere. "Yes, I am looking for a condensed, one-line solution, since ..." That's just wrong in many ways – user123444555621 Jul 02 '13 at 19:51
  • @Pumbaa80 No it's not. Read the "since," don't ignore it. – djechlin Jul 02 '13 at 19:52
  • you might want to add negative number avoidance in your requirements too. `-10` isn't much use as a delay, but whether it goes to `0`, `10` or `24` will perhaps create yet more exciting debate – Tim Jul 02 '13 at 20:07
  • There's no real issue here. This is off topic, and should be on http://codereview.stackexchange.com –  Jul 02 '13 at 20:34
  • 1
    @CrazyTrain avoiding duplicated code is a real issue, as is avoiding buggy code, and StackOverflow generally treats "what's a good way to do this?" questions as germane. In fact, questions that do not already have a working way to do it are often DVed/closed for lack of research effort (even though "there's no real issue," since it's solved), and this question has received UVs on the basis of documented research. I'm expecting answers to rely on resources outside of the code itself, including knowledge of Javascript patterns and ecosystem, which makes it inappropriate to CR. – djechlin Jul 02 '13 at 20:43
  • 1
    Not sure if you've noticed, but votes are pretty much meaningless on StackOverflow. You have a working solution. You just want it to look a little different. I have no idea how you came to the conclusion that an answer requiring knowledge of JavaScript patterns and ecosystem somehow makes it inappropriate for CR. From the site: *"Please specify what kind of feedback you're looking for: code correctness, best practices and design pattern usage..."*. Again, there's no issue here, other than you thinking the solution looks nice. –  Jul 02 '13 at 21:01
  • @CrazyTrain okay, CR could arguably be appropriate, but I tend to think of them as more "whiteboard" problems, which this is not. SO is also appropriate, and higher volume and a more standard reference, so I (and most everyone who asks questions) posted here to get prompter help. Votes are not meaningless, please see e.g. http://meta.stackexchange.com/q/9508/183887, which covers this. You keep saying "solution looks nice," but for the fourth time, keeping code DRY is a problem to which I need a solution, so I posted here. If you insist there is not a problem yet again I will politely ignore. – djechlin Jul 02 '13 at 21:34
  • If you want your comment to stick around, I recommend putting it into an answer. The comments aren't a good place to discuss various caveats because it's hard to follow. Generally, if you find yourself trying to make code in a comment, don't. It's better for everyone if you spell it out in an answer. – George Stocker Jul 03 '13 at 12:07

5 Answers5

4

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.

Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
  • `isFinite(parseFloat(delay));` :P – rlemon Jul 02 '13 at 19:37
  • @rlemon ew. Not really short. – John Dvorak Jul 02 '13 at 19:41
  • 1
    +1 for your last point, just because it's Javascript doesn't make clever code any more readable than in any other language. – Esailija Jul 02 '13 at 19:45
  • I know your last point. I'm trying to find a reasonably recognized Javascript pattern that does this clearly without making the reader have to go through as much thought as I did. – djechlin Jul 02 '13 at 19:48
  • if you want whole numbers then I would use `delay = isFinite(parseInt(delay, 10)) || 24` - both readable (albeit slightly verbose) and still in a single line. – rlemon Jul 02 '13 at 19:49
  • ***AAAAAAnnnnd*** after making all of these comments I realize you probably don't want to set 'delay' to the return of the check :P – rlemon Jul 02 '13 at 19:50
4

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.

Florian Margaine
  • 58,730
  • 15
  • 91
  • 116
  • i thought OP wanted a one line answer? – dandavis Jul 02 '13 at 19:48
  • `lookMa && noIfStatement() && imSoClever() && i++` – Esailija Jul 02 '13 at 19:50
  • Per my question: "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." Why should my reader even have to go through the logic of a function? A dense one liner is probably worse since it's more error prone, but a function with a few lines of logic expressing one simple thought can often be cleanly and readably avoided in JS. – djechlin Jul 02 '13 at 19:50
  • @Esailija perfectly readable bash code, not clever at all. Would strain most Java developers though. – djechlin Jul 02 '13 at 19:52
  • @djechlin remember that code is written once and read a lot more times. Why would you obfuscate your code when you can have a simple 2-3 liners that's waaay more readable than your obfuscated one-liner? – Florian Margaine Jul 02 '13 at 19:53
  • I'm emphatically not. In fact, I'm posting to SO asking for a simple line that is **not** obfuscated. And if I write a simple 2-3 lines and am feeling particularly generous, I will submit a poll request to underscore or lodash, then post that method as an answer to this question, so there aren't 60 2-3 line functions to be read many, many, many more times littering my code base and everyone else's. – djechlin Jul 02 '13 at 19:55
  • This is the only solution. It seems that OP doesn't want functions that meet his specific needs littering his code base, but he does want them littering the language. –  Jul 02 '13 at 19:59
  • @CrazyTrain or lodash, underscore, and other libraries that exist exactly for this purpose. – djechlin Jul 02 '13 at 20:09
  • @djechlin Your requirements are very specific. I doubt that any library will include such function just for you. – user123444555621 Jul 02 '13 at 20:10
  • @Pumbaa80 I could imagine lodash having a method like `||` but treats additional values as truthy or falsy. I would have to think about what the design of such a method would look like. And as for receiving nonnegative integer user input, that's exactly the kind of thing that tools like commander.js do. Since I do this in about every test utility I write, I may fork / submit pull request to that project. At present they recommend just using `parseInt` with no radix as a callback, so there is room for improvement. – djechlin Jul 02 '13 at 20:13
  • @CrazyTrain well, I was wondering if a language, lower level library, or existing library method already existed, so I came to SO to ask. Before asking this question it was plausible to me there wouldn't be a library feature since there was a clean way to accomplish this within the language. – djechlin Jul 02 '13 at 20:16
  • 1
    @djechlin: Alright. Your question states that you're a novice looking to learn good patterns. So here you go. When you have a set of specific requirements that doesn't afford clean, concise code, roll it into a function. If you find a library that happens to have a function that meets your requirements, and if that library provides enough additional benefit, use it. If you want to find out if a particular library has a particular function, read its documentation. If you have an expectation that any set of requirements you might encounter should already be met for you, lower your expectations. –  Jul 02 '13 at 20:28
  • @CrazyTrain yes, and I didn't know whether my specific requirements afforded clean, concise code, so I posted to StackOverflow. I didn't know whether this is covered in a commonly used library, so I posted to StackOverflow. And if my expectations turn out to be too high, then the right thing to do isn't to lower them, it's to write and publish the needed utility function. – djechlin Jul 02 '13 at 20:30
  • @djechlin: You pretty much know the answer. There's no real problem to solve, other than having code that looks nice to you. Try http://codereview.stackexchange.com/ next time. –  Jul 02 '13 at 20:34
  • @CrazyTrain FTR, no, I didn't know the answer (thanks though?). Avoiding duplicated code is a real problem to solve, as is avoiding buggy code. This is more appropriate for SO than it is for CR but I am not having that discussion here, beyond pointing out this question has done fine in vote count, quality of answers generated, and lack of closure votes. You can vote to close as off topic or raise on meta if you feel strongly (I'm taking a guess you've already downvoted). – djechlin Jul 02 '13 at 20:37
1

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.

Tim
  • 8,036
  • 2
  • 36
  • 52
  • Would downvoter care to explain in a comment? This solution passes all 7 of the given bullet point requirements. – Tim Jul 02 '13 at 19:43
  • Well, I upvoted. Regex is very much arguably the right thing to do here, when receiving user input as string. JS utility methods can't quite put together something as reasonable as `(\d+)`. – djechlin Jul 02 '13 at 19:44
  • Accepting this answer because it's probably the best advice against my problem of validating user input. Unfortunately I merged the two parts of the question together uncleanly - validation and passing the value around, and the second half of this question seems to have no great solution. – djechlin Jul 03 '13 at 19:02
0

How about:

delay = isNaN(parseInt(delay, 10)) ? 24 : delay

(Edit: I now see this suggestion in the comments. That happened while I was working this out in a console, honest. :D)

If you absolutely require that strings with leading numbers not be valid, you're going to have to typecheck it:

delay = typeof(delay) === "number" && isNaN(parseInt(delay, 10)) ? 24 : delay

For example, if I use your idiom:

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

And "delay" is `"123abc", your method will result in delay still being "123abc", since it is a truthy value.

Per Shmiddty's comment, if you aren't going to do the explicit type check, you need to use parseInt to ensure that you're actually setting delay to an integer type; even if 0 were truthy, doing delay = delay || 24 would set delay to a string value if one were passed without any kind of coercion or type check. Coercing to a number before doing the isNaN check ensures that anything that isn't a valid number (as parsed by parseInt; strings with leading numbers will still slip by) ensures that when you check isNaN, the result will mean "this is a valid number" or "this is not a valid number", rather than "this is NaN" or "this is anything except NaN".

If you're doing the type check, you don't strictly need the parseInt, but I'd argue that it's still a good idea from a semantics perspective, since it indicates that you expect and require an integer for that value.

Chris Heald
  • 61,439
  • 10
  • 123
  • 137
0

don't do this, even though it can literally do exactly what you want:

Number.prototype.valueOf=function(){return this||"0";}
alert(  0 || 24 )// shows: 24

i think it would break other scripts, but it would be nice if we could do stuff like this at a block level in javascript...

dandavis
  • 16,370
  • 5
  • 40
  • 36