1

When providing JavaScript's parseInt with a non-parsable string it returns NaN. I'm trying to understand the reasons for designing a parsing function this way.

When I write a parsing function I usually return null or throw an exception in case the input is not parsable. It seems to me that NaN is unsafe because it allows the code to keep running even when there is no value to work with.

For example, this will not throw any runtime error:

parseInt('a') + 1

But this could lead to unexpected behavior when you expect something to be an actual number while it's actually NaN. But perhaps there is a benefit to doing this that I'm not seeing.

So my question is: Are there significant dis-/advantages to the solutions below?

  1. Returning NaN
  2. Returning null
  3. Throwing an exception
  4. Something else
18
  • 1
  • 9
    This is JavaScript. You were expecting it to make sense? Jan 14, 2021 at 15:27
  • 4
    Also, why would checking for NaN be any different than checking for null? Jan 14, 2021 at 15:28
  • 1
    Design decisions made in the past don't have to make sense. JavaScript was designed in ten days, 25 years ago. It was intended to do small scripting things, but has since been co-opted to produce entire applications. Compare that time frame with the design of Clojure, which Rich Hickey spent 18 months on. Jan 14, 2021 at 15:51
  • 4
    I would argue that returning null and returning NaN are pretty much equivalent: both require the caller to check the result, and will result in errors of one sort or another if they don't. There may be differences in how convenient the language / context makes that check, but they're fundamentally both saying "you asked for an integer, but I can't give you one, so I'm going to give you something else instead".
    – IMSoP
    Jan 14, 2021 at 16:27

5 Answers 5

11

Parsing functions are a complex beast, because they must be designed to fail. There are typically at least as many invalid inputs as there are valid ones. (E.g. for parseInt there are countably infinite valid inputs and countably infinite invalid inputs.)

Some common approaches to deal with this problem are:

Return an optional type

Many languages / standard libraries have a type that represents the concept of "a value that may or may not be there" (or "zero or one of some thing"). This is typically called an Option or Maybe type. You can also think of it as a collection of length "at most one".

In fact, an Option type is isomorphic to a collection with at most one element, which means that it can implement all the collection APIs and thus be used like any other collection.

Here is an example from the Scala standard library: scala.collection.StringOps.toIntOption returns an Option[Int]. So, for example

"10".toIntOption
//=> Some(10)

"foo".toIntOption
//=> None

This is personally my favorite. Option types have some really nice properties: they are not only monads, they are also collections. Collection operations are some of the most important operations in programming, so it is likely you are already proficient in them, and you can apply everything you know to Options as well.

For example, you want to execute a side-effect with the value if it exists (e.g. print it)? Well, just iterate over the "collection": if it is empty, nothing will happen, and if the value exists, the body of the iteration will be executed exactly once.

So, instead of

const i = parseInt("hello");

if (!isNan(i)) {
    console.log(i);
}

simply do

for (const i of tryParse("hello")) console.log(i);

Want to transform it? Use map. Have a nested tower of Options? flat it. And so on. If there is a None at any step of the calculation, it will just be propagated, but it will never fail. And you can ignore the "Optionness" most of the way, until you actually need the value. Then you need to extract it from the Option and decide what to do if it is not there.

Typically, there is a getOrElse(default) method that either returns the value or a default value of your choosing.

Return an Either type

An Either type, as the name implies, is a parametric type constructor that represents the concept of "either this type or that type". It can be used for many different things, but it is often used to return either an intended result or a description of what went wrong.

An example is the Data.Text.Read.decimal function in the Haskell text package.

When we look at the type of the decimal function and unpack it a little bit (and assume a concrete type for the Integral a type constraint), we can see that it basically has type

decimal :: Text -> Either String (Int, Text)

So, it is a function that takes text as input and returns either a string (containing an error message) or a pair of an integer and the remaining text to be parsed.

If you aren't used to reading Haskell type signatures, this could by roughly translated into e.g. C# like this:

Either<string, (int, string)> ParseDecimal(string text)

[Note that in the Haskell code, String is the traditional text type from the original Haskell specification, which is just a type alias for [Char], i.e. a list of characters, whereas Text is a more modern encoding-aware type.]

Return an Error type

An Error type is similar to an Either type in that it represents the concept of returning either one type or another type. But it is more constrained in two ways:

  1. Only the "happy" type is a parameter, the other type is fixed to be some sort of type that represents an error. (Often a string with the error message, a pair with an error code and a message, or when retrofitted to a language with exceptions, an exception type).
  2. The type is biased towards the "happy" type. This means that in many contexts, it can be treated like an Option of the "happy" type, whereas an Either typically always needs to be pattern matched or deconstructed, to check whether we are in the "left" or the "right" case.

So, an alternative to the above Haskell method could be:

decimal :: Text -> Error (Int, Text)

This has the same advantages of a Maybe / Option type, except instead of just being informed that there is no value, you also get to know why.

Throw an exception

This one is obvious: when the string does not contain a valid integer, you use the error handling facilities of the language. For example, that's what the Kernel#Integer method does in Ruby:

i = Integer("hello")
# invalid value for Integer(): "hello" (ArgumentError)

The main disadvantage is that this is kind of annoying: failing to parse an input is not an exceptional situation. It is completely normal. People make mistakes, typos. Files get corrupted. Your network daemon gets bombarded by junk from a previous connection that hasn't fully been cleaned up yet.

Exception handling is typically semantically and syntactically (and sometimes also pragmatically) heavyweight. That's a fair price to pay for an actual serious problem, but not for something that is completely normal.

"Guard" the result with an additional success indicator

This is a term I just made up, so don't be surprised if you can't google it :-D

What I mean by that, is something like what .NET does with the TryXXX idiom: methods that might fail are named TryXXX and they return a bool that tells you whether the method was successful or not, and the actual return value is returned by reference in a reference parameter.

E.g. the type of int.TryParse is:

bool TryParse(string s, out int result)

And it is used like this:

int i;
if (TryParse("42"), i) {
    // do something with `i`
}

You can do the same thing by returning a tuple:

(bool, int) TryParse(string s)

var (success, i) = TryParse("42"));
if (success) {
    // do something with `i`
}

Or in a language like Go that supports multiple return values:

TryParse(string s) (int, bool)

if i, ok := TryParse("42"); ok {
    // do something with `i`
}

In Go, this is called the "comma ok" idiom, for obvious reasons.

Any language that has tuples, records, heterogeneous lists, objects, or really any kind of complex values supports (a variation of) this – instead of returning multiple values like in Go, you wrap the values into a light-weight data structure. E.g. in ECMAScript:

function tryParse(s) {
    return { success: /* … */, result: /* … */ };
}

const { success, i } = tryParse("42");

if (success) {
    // do something with `i`
}

// or

function tryParse(s) {
    return [ /* … */, /* … */ ];
}

const [success, i] = tryParse("42");

if (success) {
    // do something with `i`
}

Personally, I very much prefer an Option, because then I can let the collections library deal with all the troubles. In particular, I can manipulate the value without ever having to check if it actually exists. I can leave that check and that decision to some other piece of code. Why should the computation code know how to deal with missing values? That's the User Interface's job!

Return a union type

Some languages have a union type A ∪ B, i.e. a type whose set of values is the union of the sets its constituent types A and B.

For example in Scala 3:

def tryParse(s: String): Int | false

val i = tryParse("42") match
  false => /* whatever value you want to use */
  Int(i) => i

I don't find this particularly good API design. That's not what union types are meant for. But it is a possible API design.

In a dynamically typed language, this is arguably more sensible. For example, in Ruby, the only two falsey values are false and nil, every other value is truthy, including all integers. In particular, unlike many other languages, 0 is truthy in Ruby. Therefore, something like this would actually make sense:

def try_parse(s)
  return false if no_valid_integer?
  return i
end

i = try_parse("42")

if i
  # we know `i` is an integer and the parse was successful
end

Return a "marker" value

In order to indicate failure, return a value that is a member of the return type, but not a possible value that can be parsed.

That is technically what ECMAScript's parseInt is doing. There is no integer type in ECMAScript, only the number type which is an IEEE 754-2019 binary64 double precision floating number. NaN is a valid number but it is not a valid integer, so by returning NaN, we can signal that the parse failed, but we are still returning a value of the same type.

Return a default value

Another possibility is to return a default value if the parse fails. For example, in Ruby, conversion methods (to_X, to_XYZ) are not allowed to fail. Either you must always return a valid result, or you are simply not allowed to implement the method. the to_X methods are very lenient, the to_XYZ methods are very strict. So, for example, String#to_int does not exist, because it is not possible to always parse a string into an integer while also being strict about the value. String#to_i OTOH does exist, and it simply returns 0 when it cannot parse the string.

This has the big advantage that it never fails and always returns a valid result, but it has the big disadvantage that you have no way of knowing whether the string actually contained a valid integer or not.

Return a null value

This is what you propose in your question with returning null.

In languages like ECMAScript, where null is coercible to almost anything, this can easily lead to hiding errors. For example, null + 42 === 42, whereas NaN + 42 === NaN. So, by returning NaN, we can at least tell that somewhere, something went wrong, even if we don't where, what, when, and why. But with null, it is just swallowed, in the end, you have no indication that it was even there.

Return a null reference

These two often get confused. However, a null value is still a value. A reference to a null value can still be dereferenced. Dereferencing a null reference, however, will blow up in various ways. It might be a reasonably safe way as in Java, where it is guaranteed to throw a NullPointerException, or it might be completely unpredictable as in C.

Out of all the options, I personally find this the worst one.

2
  • “Null value” seems bad in JavaScript. In Swift, returning nil is absolutely fine - there is no way getting around checking for nil values right where they are returned.
    – gnasher729
    Jan 16, 2021 at 9:19
  • Ruby would be similar. nil in Ruby would be pretty idiomatic, actually. It starts to break down for something like JSON or YAML parsing, where nil would actually be a valid value. Jan 16, 2021 at 19:19
7

No, there is no useful way to decide within the parser function.

Converting primitive types into each other is a sufficiently basic operation that it can be used in infinitely varied circumstances and purposes. You have no idea what the result be used for, how important it is, etc. Only the caller can know whether a conversion failure can be tolerated or not. Therefore the only thing you must do is pick a convention, document it clearly and stick to it.

3
  • In which scenario’s would failure be tolerated? I can’t imagine a use case where someone’s like “Well, the result is not a number but I’ll use it in my calculations anyway” Jan 14, 2021 at 18:41
  • 1
    @DuncanLukkenaer: Such behavior would often be appropriate in interactive applications which continuously update a read-only text field to show the results of performing some computations on a user-editable field. If the user enters a valid number, the field should show the correct result as soon as the input is valid and complete, whether or not the application knows it's complete. If the user knows the field isn't complete, the user will know that the result isn't correct yet, and if the input is recognizably invalid, having the result show as NaN may be nicer than triggering a pop-up.
    – supercat
    Nov 16, 2022 at 18:04
  • @DuncanLukkenaer: Such behavior may also be appropriate in many match-mode operations which should process records independently, and receive a mixture of valid and invalid ones. Indicating that parts of the output can't be meaningfully computed would be better than having the presence of some invalid records prevent any useful output from being produced.
    – supercat
    Nov 16, 2022 at 18:05
0

After the discussions in the comments I'm able to answer my own question.

Returning NaN

This isn't recommended because this can lead to unforseen and subtle bugs due to JavaScript allowing operations like NaN + 1.

The reason it's designed this way in JavaScript is likely because it wasn't fully thought through.

Quoting @RobertHarvey:

JavaScript was designed in ten days, 25 years ago. It was intended to do small scripting things, but has since been co-opted to produce entire applications. Compare that time frame with the design of Clojure, which Rich Hickey spent 18 months on.

Returning null

This is pretty similar to NaN because JavaScript still allows using null in calculations, e.g. null + 1 === 1. So this still lead to bugs, perhaps even more dangerous ones because you end up with an actual number as opposed to NaN.

However, one benefit of using null over NaN is that TypeScript will prevent you from using null in calculations. This isn't the case with NaN because NaN is considered a number.

Throwing an error

This is the safest way to handle an invalid value because it prevents the caller from accidentally using the invalid value in further calculations.

The downside to this is that a try-catch is required for each invocation.

Conclusion

In JavaScript: Throw an error

In TypeScript: Return null

5
  • 1
    The reason JS uses NaN is because its "number" type is an IEEE floating point value, so it already has operations with that result, as well as Inf and -Inf. If it had a separate integer type, parseInt would probably be defined differently.
    – IMSoP
    Jan 14, 2021 at 16:56
  • @IMSoP That explains why NaN exists, but not why it's the return value of parseInt. I guess they might have just thought "Hey, we have a value called NaN, so we might as well return it when we can't return an actual number". But I guess what you mean is that if there would be an integer type, then NaN wouldn't be an option. Jan 14, 2021 at 17:12
  • 2
    Considering that "NaN" is short for "Not a Number" it is quite reasonable to return it if you can't return a number :-) The huge advantage of NaN is that you it propagates through all kinds of calculations, so you can do all kinds of maths and check if the final result is NaN. For example adding three numbers parsed with parseInt will give NaN if parsing any of them fails.
    – gnasher729
    Jan 15, 2021 at 13:37
  • @gnasher729 Yeah it makes sense to return it. I guess what doesn't make sense is that NaN actually is a number (typeof NaN === 'number'), so propagation can be unexpected Jan 15, 2021 at 13:40
  • 1
    It's because JS was designed to add 20 - 200 lines of interactivity to webpages. Sadly if you give some people a spade first they will refuse to use an excavator because "the spade is easier". It's on of the paradoxes in programming that can make it very challenging. The entire JS ecosystem today is basically just the blind leading the blind imo.
    – Cobolt
    Mar 22, 2023 at 8:36
0

In Ruby there are two functions I typically use to parse an integer: to_i and Integer() (there very well may be others).

  • to_i returns 0 given a non-integer input.
  • Integer() throws an error given a non-integer input.

I regularly use them both depending on what would be the most useful result in the caller.

I imagine if you only had an integer parser that threw an exception someone would eventually find that they're constantly catching and ignoring that exception and abstract that behavior to a function that returns null.

If you only had an integer parser that returned null I bet someone would eventually find that they're throwing exceptions a lot when they get a null return value and abstract that behavior.

Is there a significant benefit to one of the solutions below?

I think it will always depend on what you need to happen next in your caller.

Since NaN and null are both falsey I imagine for the most part which you return wouldn't really matter. But if NaN was significantly different from null to multiple parts of your code, you might end up needing a version that returns NaN.

-1

In case of such function there should be at least one tryXXX which would return bool and and actual result (if any). There could be also added function XXX which simply calls tryXXX and in case of getting false it would throw an exception.

3
  • 1
    This Try-Parse pattern is common in .NET, but how would you implement this in JavaScript with the lack of an out parameter? Jan 15, 2021 at 13:51
  • For example return [ok, i]; or return { ok: ok, value: i };, or a very stupid way: function tryParse(str, ret) { ret[0] = i; return success; }; const iContainer = []; const success = tryParse("hello", iContainer); if (success) { const i = iContainer[0]; } Jan 15, 2021 at 16:32
  • @DuncanLuk, granted in .Net is very clean pattern, but in JS like Jörg wrote. The point here is to avoid throwing an exception if you don't have to, yet you provide meaningful API. Throwing exception is very costly, thus it is better to have a pair of those functions. Jan 16, 2021 at 6:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.