You've argued that having `null >= 0` is the best that JS could do while having a coercion-based semantics. And I agree: this confusing behavior may be the best you can possibly do with a coercion-based semantics. And it's not just this example, either. JavaScript is filled with gotchas, and a lot of them have to do with automatic coercion. For example, did you know there are number and string values x, y, and z, such that x<y, y<z, and z<x?
But if this is really the best you can do in the presence of automatic coercion, maybe that's an argument against coercion.
x = ''
y = '-Infinity'
z = -1
x < y // true: '-Infinity' (y) doesn't get coerced into a number, so '' < 'any string with at least 1 character'
y < z // true: '-Infinity' (y) compares with a number, so it coerces to -Infinity, which is less than -1 (z)
z < x // true: '' (x) is compared with a number, so it coerces to 0, which is greater than -1 (z)
Thank you for the brain exercise!
P.S. - No one ever use crap like this in production code!
Again though < and > are numerical operators as poster pointed out so comparing a number to a string using these operators is... 'nonsensical' if that's perhaps the right term.
Consider checking if object foo > 1. What would that mean?
Well, I disagree. You have to add a third condition for this to be the best you can do, and that is that the boolean type has to be two-valued. But that is not a given. Just as numerical types can include infinities and NaNs, a boolean type could include a third value that is neither true nor false. The IF statement would have a third clause to handle this value, something like:
IF condition
THEN [code if condition is true]
ELSE [code if condition is false]
OTHERWISE [code if condition is neither true nor false]
Like ELSE, the OTHERWISE clause would be optional so this would be a backward-compatible change. By default, existing code that encountered non-true-non-false conditions would do nothing.Now, if you don't know the type of x and try to use it as boolean without being careful, it might look like it has four values. That comes partly from being dynamically typed and partly from performing coercion instead of throwing errors. So one might argue that Javascript encourages people to write code that's not careful, or that it's very clumsy to write careful code in Javascript, but saying booleans have four values is just nonsense.
No three-valued Boolean algebra exists.
For my money, the only sane thing to do with e.g. "null >= 0" is to throw an exception. (Even better would be just rejecting it at compile time, but JS obviously doesn't have that luxury.)
[1] https://en.wikipedia.org/wiki/Null_(SQL)#Law_of_the_excluded...
EDIT: Ninja edit: I do understand that NaN has 'reasons', but ideally it'd really want NaN == x to trap rather than just doing 'the weird thing'.
JavaScript is horrid :(
This is obviously a bit of an Argumentum ad Absurdum, but I think it makes the point pretty well? There are concrete, important differences between languages regarding error-proneness, etc. The reality is that humans are lazy, fallible, etc. and we will make random mistakes, so if we can prevent mistakes (or at least catch them early), then that may be a worthwhile trade-off. Especially for such a trivial case. When was the last time you actually needed "null >= 0" to be true, for example? :)
[1] https://en.wikipedia.org/wiki/Malbolge (pleasantly surprised to see that it has a Wiki entry)
bool? t = true;
bool? f = false;
bool? u = null; // "unknown"
Then you can do things like (t & u) or (f | u) or (!u), and it works as you'd expect. But you can't write: if (t & u) ...
for example, because "if" requires a definite true or false. So e.g. if you want it to execute only if your expression is definitely true, you do: if ((t & u) == true) ...
etc. Consequently, you don't get silent bugs because a null slips through somewhere - if your expression ends up introducing nulls at some point, you always have to decide what exactly it means for it to be null at the end of the pipeline.Coincidentally, it also doesn't allow (t && u) or (f || u), for the same reason why it doesn't allow "if" - because the operators are short-circuited, and whether the second operand is evaluated or not has observable side effects, so null/unknown cannot be handled safely.
In SQL, on the other hand, the use of NULL is a conditional implicitly does what I did explicitly above - i.e. NULL is basically treated as FALSE - which IMO is wrong in principle, but more importantly, results in silent, hard to detect bugs.
(Oh, and BTW, before you completely dismiss the idea that my suggestion might have merit, you might want to look me up. I didn't just fall off the turnip truck.)
Don't get me wrong - I don't know who you are but your username is in my mental bucket of "people worth paying attention to" just from past comments. I ask because the idea of a three-valued boolean seems rather more "magical" than whatever sins JS already commits by defining comparisons such that (a<=b || b<=a) can be false, and so on. I mean, isn't it a contradiction in terms to begin with, to call such a beast a boolean?
if (foo.bar.length >= baz.buz) {
...
} otherwise {
// what am I supposed to do here?
}
How do I disambiguate if the "broken" type coercion was actually what I wanted? How do I tell what circumstances conspired to make this statement not-clearly-true?But in the absence of this information, my first guess would be:
if (foo.bar.length >= baz.buz) {
...
} otherwise {
alert("Something unexpected happened. Either foo.bar.length or baz.buz is not a number.")
}Isn't what you're describing here basically equivalent to saying JS should throw on invalid comparisons, with
try {if (x) then A else B} catch C
rewritten as if (x) then A else B otherwise C
?But WHY are you trying to compare them? The fact that one of these variables is a field called LENGTH is highly suggestive of some particular semantics that you intend these variables to have.
> there's a dozen different ways where that comparison could involve type coercion
That's right, which is the reason I can't answer your question as you posed it.
I predict that if you try to fill in the details what you will find is that you will be unable to do so in any way that does not reveal the presence of something badly wrong in your design.
> The "third boolean" value is useless unless you completely redesign the language.
You have to add an OTHERWISE clause to the IF statement and change the semantics of some operators. That's not a "complete redesign."
Interesting way of expressing the result of an ill-defined operation. Or am I off base?
Jokes aside, a 3 value Boolean conditional could have some useful applications.
Full disclosure: I downed his 'look me up' post and upped his more substantive posts.
The comment speculating on the "OTHERWISE" clause is the kind of brilliant off-beat thinking that challenges the level of understanding of the reader.
The people scoffing at and downvoting it are, however smart and knowledgeable they may be, ignorant or foolish. If they thought it through they might learn something.
It's a perfectly valid response to say, "Hey wait a minute, I know what I'm talking about here." It's not appeal to authority, and it is certainly not an ad hominem since the people he's talking to have shown that they don't understand.
Those words must have new meaning since he was citing as an example a language that's existed since the 70s. Ternary booleans are far from a unique or novel concept. The main difference is that for most languages, including Javascript, that third value exists at the variable binding or expression level, not the value level. Other languages that don't allow nulls encode that third value in a Maybe type. SQL, being a thin abstraction over data storage that needs to be able to encode nulls, doesn't make that distinction. That his idea was poorly worded to state that the actual Javascript value would have a third possibility rather than the comparison expression being nullable doesn't add to his credibility. The distinction between an expression evaluation, variable binding and a value isn't exactly a newbie concept, but for someone with the credentials he's claiming, I'd expect him to know it and be more precise.
> since the people he's talking to have shown that they don't understand.
Perhaps you should go back and read the exchange. The comment he was replying to was specifically about the mixture of Javascript and ternary booleans, not just ternary booleans. Also, if you re-read the comment that you're calling ignorant in the context of the authors original confusing of expression evaluations and values there's at least the possibility that he's making a very valid point, albeit in a way that can be read as confrontational. Javascript could already add an otherwise block without adding a third boolean value...all that would be necessary is to allow comparison operators evaluate to null or undefined and then execute the otherwise block in that case. Adding a third value would mean that a boolean variable binding could now have 5 values, true, false, not_true_or_false, null or undefined. That's nonsensical and deserves to be called out. It's bad enough that the language has null and undefined, since it causes a ton of confusion that could have been avoided. Adding some special "not null but sort of null" boolean value would just add more confusion to the language.
I still disagree about the argument from authority, but I think that's subjective.
Warm regards.
FWIW, I actually agree with this. There's no need to add a new value. It would be fine with me (within the context of Javascript's already horribly broken design) to use an existing value (null or undefined) as the third "boolean" value.
The part that really matters is that whatever null>=0 returns it should be different from either 1>=0 or -1>=0.
I was merely explaining why I downed your other comment. I don't care if you're a fresh bootcamp grad or the former lead on AdWords...your argument is equally compelling. I'm calling it out because I think it discourages people from engaging in conversations where they can learn. If you are who you say you are and have the experience I think you do and someone significantly more junior disagrees with you, it's an opportunity for you to explain your thinking in a way that helps them improve theirs going forward. Instead, you took an expedient and, IMHO, intellectually lazy route. It's a small thing, but it's all part of making our field more welcoming to newcomers. What JavaScript beginner is going to argue with the ex-AdWords lead? Beyond being an appeal to authority, your comment possibly shut down someone's positive learning experience.
And if it seems like I'm quibbling with you or being overly hard on what you've said, that part is definitely about who you are. I feel like those of us who've been in the industry a while (I'm a few years behind you, but I'm still nearing my 20th year doing this stuff) have an obligation to be better about technical discussions and work towards positive conflict, even if the other side seems intent on going negative. It's hard and I fail at doing it constantly, so I say this with the full realization that I'm often the proverbial 'pot' to your 'kettle'.
FWIW, I totally buy the former. If (null>0) returned null, then most things would still work as expected, and you could easily check the result of the comparison to catch unintended behavior. One can easily imagine a world where JS was defined this way from the start, and it'd be pretty much the same language.
The latter (three-way IF control structures) strikes me as waaaay more tenuous - much bigger change, more complexity on the programmer, and no benefits I can see that you wouldn't get from the former bit.
IF (x<y)==null {
[otherwise]
} ELSE IF (x<y) {
[then]
} ELSE {
[else]
}
That seems awkward to me.For example, I suspect that three-way IFs like you describe would only really get used in two ways - often the second and third branches would be identical, and the rest of the time the third branch will be some kind of "console.warn('bad inputs!')" kind of error handling.
For the latter of those two cases, testing for the error case before proceeding to the regular logic (as in your code sample above) seems intuitively like the right thing to do - analogously to how you might check (isNaN(operand)) before doing some math. And in the former case, if the second and third branches are identical you'd almost certainly want some syntactic sugar to avoid writing the same logic twice, like "IF (bool) {...} ELSE-AND-OTHERWISE {...}" -- which would just be isomorphic to what we have now. You know what I mean?
Coming in hot with "oh but fallacies, hmm hmm, heaven forfend" when what's being suggested is "maybe don't be a jerk?" is not a great look, don't you think?
Go back and look at what he wrote. He basically said, "before you dismiss my idea as not having merit, go look me up." Ideas have merit independent of who has them. That's the point. He was citing his credentials to back up his earlier argument.
I'm not saying that the comment that he was replying to was right or even civil and had he left off his second "Oh, BTW..." line, it would have been a reasonable response. But including it not only speaks to the comment he was replying to but also to everyone who disagrees with his idea.
BTW...parodying what I said followed by condescension is also not a great look.
Lisper wasn't saying, "I'm right because I'm me."
He was saying, "Adjust your prior[1] for my statement given additional data about its source."
> Oh, and BTW, before you completely dismiss the idea that my suggestion might have merit, you might want to look me up.
He wasn't saying "I'm right because I'm me." He was saying, "I'm me so don't assume I'm wrong." It's still an appeal to authority. Had his credentials been part of the first line, where he was confirming that he was serious about his previous post, then your interpretation would be correct.
Wow, you have quite the exacting standards.
If you call that exacting standards, so be it. I call it reading comprehension.
Indeed it does. So let's recall the context in which I made my original comment:
> Are you seriously suggesting that, widgety though JS's implicit type conversions might be, if its booleans had three values then things would be better?
That seemed to me to be a pretty non-constructive question, essentially a passive-aggressive way of saying, "You can't possibly be serious. You must be a complete newbie dweeb to come up with such a dumb idea."
That's why I responded the way I did.
For example, many languages treat conditions as "if nonzero". From a certain perspective, you could argue that
if (i--) {
is "clearly faulty code".From another perspective, you would say it's a shorthand whose meaning is obvious to anyone familiar with the rules of the language.
In my view, JavaScript's sin is that its rules are complicated, and it's difficult to statically analyze, so tools intended to guard against its pitfalls can only do so much.
Whether or not the complicated rules are about faulty code is pretty irrelevant. ASI is an error correction mechanism, but its pitfalls are easy to detect, so in modern environments it doesn't matter. The behavior of comparison operators seems to me like more of a convenience feature, but it's still a problem; the rules are easy to forget, and only a very draconian linter rule can protect you.
That said, objectively it doesn't gain that much. It may be that the cost of having any kind of implicit coercion is bigger than such gain. Implicit numeric coercion is another topic to think about, it is also hard to be sure if it is a net gain.
If you're using JavaScript, I encourage you to embrace the dynamic, coercive funky nature of the language. There is a twisted logic to each twisted feature.
You will have to put comments around them just to be sure the next guy (it might even be the author himself) doesn't waste its time understanding what the hell the code is doing.
let a = 0;
for (const record of records)
a += record.bool;
What's so bad about this? (aside from mutable accumulator, but that can be restricted to one scope). The pattern is basically directly lifted from C, where there's thankfully no such thing as a boolean type. If anything, the solution is to fold Boolean and null into Number to begin with.> You will have to put comments around them just to be sure the next guy (it might even be the author himself) doesn't waste its time understanding what the hell the code is doing.
I sincerely hope that anyone who writes JavaScript on my team knows the coercion rules in JavaScript; otherwise they should probably be writing some transpiled language that lacks these features.
>The pattern is basically directly lifted from C, where there's thankfully no such thing as a boolean type.
There has been a boolean type in the current standard for C for longer than some of the younger people here have been alive.
Nonetheless, bool or _Bool (aside from having implicit saturating arithmetic and assignment) is a one-bit integer type. It seems it mostly just formalizes the (implied, fictional) narrowing conversion involved in testing conditions, which is probably why I've not seen it used explicitly. The macros true and false seem to expand to 1 and 0 respectively.
Furthermore, from this perspective, a Boolean AND is just integer multiplication; Boolean OR is saturating addition; Boolean XOR is inequality; and Boolean IMP is saturating subtraction.
a += record.bool ? 1 : 0;
if I can have less awkward type coercions in return in a heartbeat. a += !!record.bool;
There now. Happier? let a = 0;
for (const record of records) {
if (record.bool){
a += 1;
}
}
or ls.filter(x => x).length;
aren't that much harder to read. The second one probably doesn't do deforestation and would end up stupidly slow, though.For anyone wondering why (like me), it's because +'a' returns NaN, and comparisons with NaN always return false (NaN <= 0, NaN >= 0 and NaN === NaN are all false).
Specifically, the Abstract Relational Comparison algorithm for 'a' < null returns undefined (instead of true or false), and all comparison operators return false when it does that. [1]
When neither argument is NaN, what the author wrote is true and a >= b === !(a < b).
[1]: http://www.ecma-international.org/ecma-262/8.0/index.html#se...
These kinds of statements is why I think webdev is completely and thoroughly fucked. When obviously bad software or language design is widely accepted on the basis of convoluted technicalities you know that thing will not get better, only worse.
>This type of issue isn't unique to JavaScript. In Java, if you have boxed Integers
Boxed types in Java were a clusterfuck of their own. They shouldn't be used as justification of bad design in other languages. A cautionary tale, more like.
Many smart people have been working on improving JavaScript for a while now, and many of the problems have been fixed either by language improvements or widely-available tools. The runtime behavior of basic operators can't be changed, though, so we'll likely be stuck with the type coercion rules for a long time, although it's worth noting that both TypeScript and Flow disallow `null >= 0`.
That also means that the concluding section of the article is simply wrong. To be honest, it's a pretty poor read – a waste of many words to explain a spec that's pretty straightforward itself, and ending up in a major misunderstanding.
>= and > are numerical operations, so both sides are interpreted as a number, and null is 0 when treated as a number. That means `null > 0` becomes `0 > 0`, which is false, and `null >= 0` becomes `0 >= 0`, which is true. In contrast, == is a much more general operation that works on numbers as well as objects, strings, and all sorts of other things, so it certainly doesn't make sense to always convert both sides to a number first. I'm certainly happy that `null == 0` is false in JavaScript, and I wouldn't want that changed to be consistent with numerical comparison (which comes up much more rarely than equality checks, at least in my code).
This type of issue isn't unique to JavaScript. In Java, if you have boxed Integers, `==` will do object identity comparison (so two different instances of the same number will compare as not equal), while `>=` etc will unbox them and do numerical comparison, which I think stems from the same issue of equality being more general than numerical comparison.
Also worth noting that it's not always true that `a >= b` means `!(a < b)` in JavaScript. `'a' >= null` and `'a' < null` are both false.