The concept of NaN long predates the language that uses ===, and is part of a language-agnostic standard that doesn't consider other data types. Any language choosing to treat the equality (regardless of the operator symbol) of NaN differently would be deviating from the spec.
Julia evaluates NaN === NaN to true, as long as the underlying bit representations are the same. Eg NaN === -NaN evaluates to false, as happens also with NaNs where for some reason you tinker with their bytes. I think it makes sense that it is so though, regardless that I cannot think of any actual use cases out of doing weird stuff.
In R, NA (which is almost, but not quite like NaN) actually has separate types for each result, so you can have NA_boolean, NA_integer etc. Its super confusing.
It is a minor nuisance, but I think there's ultimately a pretty good reason for it.
Old-school base R is less type-sensitive and more "do what I mean", but that leads to slowness and bugs. Now we have the tidyverse, which among many other things provides a new generation of much faster functions with vectorized C implementations under the hood, but this requires them to be more rigid and type-sensitive.
When I want to stick a NA into one of these, I often have to give it the right type of NA, or it'll default to NA_boolean and I'll get type errors.
Either you throw an exception (and imo it is better to just throw an exception before that already, then) or else what you do determines what NaN === NaN actually evaluates to.
Apparently NaN (not a number) becomes false when type-cast to boolean.
Boolean(NaN)
===> false
For a hypothetical NaB (not a boolean), the same behavior seems logical.
Boolean(NaB)
===> false
So the condition `if (NaB)` is false and will fall through to the `else` branch. But..
> what you do determines what NaN === NaN actually evaluates to
I think I disagree with this because it's not about casting to boolean, it's a totally different question of self-identity, or comparing two instances (?) of a value (?!).
From the article:
typeof NaN
===> "number"
For symmetry and consistency:
typeof NaB
===> "boolean"
> NaN is the only value in the whole of JavaScript that isn’t equal to itself .. the concept of NaN is meant to represent a breakdown of calculation
Similarly, NaB would represent a breakdown of true/false condition (somehow) as an exceptional case. Whether it equals itself is a matter of convention or language design, not logic - since it's beyond logic just as NaN is beyond numbers. I would say:
NaN === NaN
===> false
NaB === NaB
===> false
> you throw an exception (and imo it is better..
I agree throwing an exception is better design for such exceptional cases - but we know JavaScript as a cowboy language would rather plow through such ambiguities with idiosyncratic dynamic typing, and let the user figure out the implicit logic (if any).
I don't necessarily think about JS in particular. A lot of languages have similar design and prob most have to deal with this issue in general.
In your examples, it does not make sense to have both
typeof NaB
===> "boolean"
and
Boolean(NaB)
===> false
But maybe if you had NaB it would make sense to evaluate
Boolean(NaN)
===> NaB
And maybe also evaluate `NaN === 1` to NaB?
I am not too fond of NaB because boolean is supposed to encode binary logic. There exists ternary logic and other concepts that could be used if you do not want strictly two values. Or if you want to encode exceptional values, no reason not to use int8 directly, instead of calling it boolean but actually using sth that could be represented as int8 (NaB has to be represented by some byte anwyay). In general, tbh, I think often it is not useful to encode your logic with booleans because many times you will need to encode exceptional values one way or another. But NaB will not solve that, as it will just function as another way to encode false at best, throwing exceptions around at worst.
> Similarly, NaB would represent a breakdown of true/false condition (somehow) as an exceptional case.
Still, in JS `NaN || true` evaluates to `true` hence I assume `NaB || true` should evaluate to true too. It is not quite the same as NaN + 1 evaluating to NaN. And as NaN (and hence NaB) functions as false when logical operations are involved for all intents and purposes, except if you exactly inquire for it with some isNaB() or sth, to me NaB is another way to encode false, which is probably fine depending what you want it for.
Apparently this is also called "three-valued logic".
> In logic, a three-valued logic (also trinary logic, trivalent, ternary, or trilean, sometimes abbreviated 3VL) is any of several many-valued logic systems in which there are three truth values indicating true, false, and some third value.
> the primary motivation for research of three-valued logic is to represent the truth value of a statement that cannot be represented as true or false.
Personally I think I'd prefer a language to instead support multiple return values, like result and optional error. Or a union of result and error types.
> no reason not to use int8 directly
Hm, so it gets into the territory of flags, bit fields, packed structs.
It should throw a compile-time error. Anything like this which allows an invalid or unmeaningful operation to evaluate at compile-time is rife for carrying uncaught errors at run-time.
What do you mean "not allowed"? Throwing a compile or runtime error? Many languages allow division by zero, and x/0 typically gives inf unless x is 0, then x/0 gives nan. If x is negative then x/0 gives -inf. Of course this all can get tricky with floats, but mathematically it makes sense to divide by zero (interpreted as a limit).
For NaNs, maybe in some domains it could make sense, but eg I would find it impractical when wanting to select rows based on values in a column and stuff like that.
NaN is, by definition, not equal to NaN because they’re not comparable, it does have a definitive Boolean representation - false is correct
The concept of NaN long predates the language that uses ===, and is part of a language-agnostic standard that doesn't consider other data types. Any language choosing to treat the equality (regardless of the operator symbol) of NaN differently would be deviating from the spec.
Julia evaluates NaN === NaN to true, as long as the underlying bit representations are the same. Eg NaN === -NaN evaluates to false, as happens also with NaNs where for some reason you tinker with their bytes. I think it makes sense that it is so though, regardless that I cannot think of any actual use cases out of doing weird stuff.
In R, NA (which is almost, but not quite like NaN) actually has separate types for each result, so you can have NA_boolean, NA_integer etc. Its super confusing.
It is a minor nuisance, but I think there's ultimately a pretty good reason for it.
Old-school base R is less type-sensitive and more "do what I mean", but that leads to slowness and bugs. Now we have the tidyverse, which among many other things provides a new generation of much faster functions with vectorized C implementations under the hood, but this requires them to be more rigid and type-sensitive.
When I want to stick a NA into one of these, I often have to give it the right type of NA, or it'll default to NA_boolean and I'll get type errors.
> When I want to stick a NA into one of these, I often have to give it the right type of NA, or it'll default to NA_boolean and I'll get type errors.
Yeah, I know. I hit this when I was building S4 classes, which are similarly type-strict.
Again, I think this was the right decision (pandas's decision was definitely not), but it was pretty confusing the first time.
What would an `if (NaB) ... else ...` block do?
Either you throw an exception (and imo it is better to just throw an exception before that already, then) or else what you do determines what NaN === NaN actually evaluates to.
Apparently NaN (not a number) becomes false when type-cast to boolean.
For a hypothetical NaB (not a boolean), the same behavior seems logical. So the condition `if (NaB)` is false and will fall through to the `else` branch. But..> what you do determines what NaN === NaN actually evaluates to
I think I disagree with this because it's not about casting to boolean, it's a totally different question of self-identity, or comparing two instances (?) of a value (?!).
From the article:
For symmetry and consistency: > NaN is the only value in the whole of JavaScript that isn’t equal to itself .. the concept of NaN is meant to represent a breakdown of calculationSimilarly, NaB would represent a breakdown of true/false condition (somehow) as an exceptional case. Whether it equals itself is a matter of convention or language design, not logic - since it's beyond logic just as NaN is beyond numbers. I would say:
> you throw an exception (and imo it is better..I agree throwing an exception is better design for such exceptional cases - but we know JavaScript as a cowboy language would rather plow through such ambiguities with idiosyncratic dynamic typing, and let the user figure out the implicit logic (if any).
I don't necessarily think about JS in particular. A lot of languages have similar design and prob most have to deal with this issue in general.
In your examples, it does not make sense to have both
and But maybe if you had NaB it would make sense to evaluate And maybe also evaluate `NaN === 1` to NaB?I am not too fond of NaB because boolean is supposed to encode binary logic. There exists ternary logic and other concepts that could be used if you do not want strictly two values. Or if you want to encode exceptional values, no reason not to use int8 directly, instead of calling it boolean but actually using sth that could be represented as int8 (NaB has to be represented by some byte anwyay). In general, tbh, I think often it is not useful to encode your logic with booleans because many times you will need to encode exceptional values one way or another. But NaB will not solve that, as it will just function as another way to encode false at best, throwing exceptions around at worst.
> Similarly, NaB would represent a breakdown of true/false condition (somehow) as an exceptional case.
Still, in JS `NaN || true` evaluates to `true` hence I assume `NaB || true` should evaluate to true too. It is not quite the same as NaN + 1 evaluating to NaN. And as NaN (and hence NaB) functions as false when logical operations are involved for all intents and purposes, except if you exactly inquire for it with some isNaB() or sth, to me NaB is another way to encode false, which is probably fine depending what you want it for.
Reminds me of something I saw recently, a tri-value boolean, a "trilean" or "tribool".
They all feel "risky" in terms of language design, like null itself. But I suppose there are languages with Maybe or Optional.> The tribool class acts like the built-in bool type, but for 3-state boolean logic
https://www.boost.org/doc/libs/1_48_0/doc/html/tribool/tutor...
Apparently this is also called "three-valued logic".
> In logic, a three-valued logic (also trinary logic, trivalent, ternary, or trilean, sometimes abbreviated 3VL) is any of several many-valued logic systems in which there are three truth values indicating true, false, and some third value.
> the primary motivation for research of three-valued logic is to represent the truth value of a statement that cannot be represented as true or false.
https://en.wikipedia.org/wiki/Three-valued_logic
Personally I think I'd prefer a language to instead support multiple return values, like result and optional error. Or a union of result and error types.
> no reason not to use int8 directly
Hm, so it gets into the territory of flags, bit fields, packed structs.
https://andrewkelley.me/post/a-better-way-to-implement-bit-f...It should throw a compile-time error. Anything like this which allows an invalid or unmeaningful operation to evaluate at compile-time is rife for carrying uncaught errors at run-time.
Or a 3rd option is it should not be allowed similar to dividing by 0.
What do you mean "not allowed"? Throwing a compile or runtime error? Many languages allow division by zero, and x/0 typically gives inf unless x is 0, then x/0 gives nan. If x is negative then x/0 gives -inf. Of course this all can get tricky with floats, but mathematically it makes sense to divide by zero (interpreted as a limit).
For NaNs, maybe in some domains it could make sense, but eg I would find it impractical when wanting to select rows based on values in a column and stuff like that.
Typed nulls are good