My Opinion on “5” == 5
win-vector.com
win-vector.com
My favorite language, C#, has some unfortunate history. The == operator when dealing with references indicates reference equality (Object.ReferenceEquals), but can be overridden to indicate value equality (Object.Equals) even with reference types. Then, when dealing with value types, == suddenly means value equality. It would've been better to define two separate operators for reference and value equality. This would've helped make usages self-documenting.
But C# also tried to improve on Java. One of the things that the latter was commonly criticized for, was the need to use .equals() for strings for value comparisons - which is almost always what you want - and avoiding the more obvious ==. So they overloaded == for strings to mean value equality, while retaining them as reference types.
IMO, the bigger mistake here was to keep strings as reference types - they really ought to be value types (the implementation can still wrap a reference to the actual string data internally, and share it between copies, but the client shouldn't be able to observe that).
There are languages with a type system that have universal type (and all types are derived from it), in these languages it make sense that "5" == 5 is defined and false.
You still have == working on all references in this case, but this can be interpreted as reference types having the default value semantics of "every instance is logically different", so referenced values are only equal to themselves.
The annoying part of this approach is that ReferenceEquals() is the one that ends up being used way more often than == in practice for reference types, but it's also much longer to type, and more awkward to use being a function. Python and VB, with their "is" operator for reference equality, handled this one best. Unfortunately, C# wasted "is" as a keyword for something far less common - a type check (and now also pattern matching).
From PostgreSQL
# select '5' = 5;
?column?
--------
t
(1 row)
I believe this is standard SQL. Putting something in quotes just means it is a literal, doesn't mean it is a string. Of course if you explicitly cast it to a string, then you get an error. As in: select '5'::text = 5;
ERROR: operator does not exist: text = integer
LINE 1: select '5'::text = 5;
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
I agree with the premise - SQL gets it right, but perhaps only in PostgreSQL?I don't agree with the premise. Both MySQL[1] and MS SQL Server [2] do an implicit cast and then compare them as numbers. I don't know whether this is ANSI standard behavior though.
[1] https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html
[2] https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-t...
let a: String = “5”
let b: Any[] = [5,6,7]
let c: Int[] = [5,6,7]
a in b // false - we don’t know what’s in b so we check and a is not in b
a in c // type error - there’s no way a is in c. Convert a to an Int, and handle the error cases involved in that.
I believe the main reason why it doesn't, is because it allows to write kinda sorta generic code that always uses == for comparison, and have it work for the most common cases for both value and reference types (defined semantically here: reference types are those where object identity matters in some way, value types are those where it doesn't). For example, functions that work on lists, or even the standard operator "in". If not for this hack, everything that needs to compare things would have to take an explicit comparator function for every separate comparison context to ensure that caller can request "==" or "is" as appropriate for their use case.
FWIW, I believe that explicitly parametrized comparisons should be the norm in generic code, not exceptions, even if it's more verbose. But mainstream PL design hasn't really embraced this. Much like how we keep repeating the mistake of making strings indexable and iterable without specifying what is being indexed or iterated (bytes, codepoints, graphemes etc) - because programmers are so used to the convenience of doing that, not because it was ever a good idea.
That wouldn't be an improvement, tho. Runtime exceptions mean you have to exercise that code-path to find something as trivially detectable as comparing a string to an integer. It has to be a compile-time check (or a validation step for an interpreted language).
To specifically address your scenario:
> If not for this hack, everything that needs to compare things would have to take an explicit comparator function for every separate comparison context to ensure that caller can request "==" or "is" as appropriate for their use case.
This is a solved problem in statically typed languages. All it takes is a protocol/interface called "Equatable" or "Comparable" or something like that. Collection.contains is only available if the collection's contents implement that "Comparable" interface/protocol. For every comparable thing, == is a required fn. A good clear example of this is `equatable` in swift https://developer.apple.com/documentation/swift/equatable
Swift takes the stance that nothing is Equatable unless it opts into the protocol. It also synthesizes a definition of == if the type is simple enough. Another language might say, ok everything is equatable, the default definition is just comparing memory addresses (a->ptr == b->ptr) - but that would still require a and b being of the same type. If you want to compare an Apple and an Orange `==` is the wrong tool. Something like `apple.sameCaloricContent(orange)` i.e. a function that describes how you're comparing two un-alike things would be the right way to handle it.
That would be an improvement, surely? Just not one that is good enough...
But at that point we're discussing a much broader problem (or feature, depending on who you ask) with dynamic typing in general, which is different and orthogonal to the problem with implicit conversions that "5"==5 is all about: whether it's an error in the first place or not.
> This is a solved problem in statically typed languages.
It's only a solved problem if you follow the patterns consistently, which many modern languages do not (e.g. look at C# and Java, which have == and Object.Equals() and Equatable, and where collections of equatable items aren't themselves equatable).
But even then, it doesn't really solve the "contains-problem", because the request is ambiguous by the very nature of a collection of references - perhaps you do, in fact, want to check if a reference to an object is present in the collection or not, rather than checking for something that is value-equal. This also goes for comparing whole collections for equality element-by-element etc. All those cases, at the minimum, need a way for the caller to specify which kind of comparison they need. Worse yet, there's no clear default here - both are common - so, ideally, it should always be explicit.
Even C# and Java define equality between all types ("5".Equals(5) is false, not an error).
Still, I wasn't trying to defend dynamic typing over static typing, just pointing out how to conceptualize the dynamic language decision in terms of type theory, to point out that it's not surprising.
A point that other commenters brought up is what to do in the case of user input. Lets assume it's unreasonable to expect the user to select a type explicitly. In this case I'd say there are actually three distinct types: int, string, unknown-currently-represented-as-a-string. This doesn't solve the problem of needing to select a type conversion, it just makes it more obvious that a type conversion must occur.
This reminds me of Go's const numbers which are actually represented as abstract numbers with arbitrary precision during compilation and only get assigned a concrete value once its assigned to a variable that has a concrete type. This allows, for example, to have an expression using Pi which can be assigned to both float and double that utilizes each type's maximum precision.
Haskell does something similar. Literal integers and decimal fractions are translated into expressions like "fromInteger (12345 :: Integer)" for "12345" or "fromRational (314159 % 100000 :: Ratio Integer)" for "3.14159" (where the % operator, defined in Data.Ratio, constructs a Ratio value from a numerator and denominator). The inputs have arbitrary precision, and the concrete type of the expression is resolved through the normal type unification process.
IMHO the static type error, runtime type error, and always-false interpretations all have some merit, though naturally static typing without any implicit conversion has the best chance of detecting mistakes early on. The only one I would reject as obviously invalid is the one that says that "5"==5 is true.
https://thenewstack.io/brendan-eich-on-creating-javascript-i...
Not discounting other implications of a 10 day design period :)
I would also not complain if a "Warning: why are you comparing strings and integers" was raised.
Operators like +,-, ... should raise TypeError, or similar.
I’d argue that there are some valid use cases for “5” and 5 to be equal. For example, if you take user input where on an admin screen for a user ID or username, you don’t know if the type is a string or number. Another common example is when you don’t have a known encoding for a packet yet, so you treat it as a binary string. In these cases, it’s easier to grok, as a human what is expected.
People usually understand the difference between "cat" and a cat.
If you want to compare input to an int, you should be verifying the input is a valid int before comparing it.
Just hoping the language will do the right thing is a great way to inadvertently allow exploits in your service
For the most part, the developers in those spaces seem to know the pitfalls and how to avoid them.
So "5" + 1 is 6, just as 5 + 1 is 6.
Perl uses .
Oracle SQL uses ||
Microsoft SQL and VBA use &
C obviously doesn't use +.
Neither does Pascal.
Power Query doesn't.Python and Ruby even overload *, for the dubiously useful construction:
"hello" * 3 == "hellohellohello"
Nope, Microsoft SQL uses +.
* https://docs.microsoft.com/en-us/sql/t-sql/language-elements...
* https://i.imgur.com/7z81o8t.png
* https://i.imgur.com/VDR9qer.png
> C obviously doesn't use +.
But C++ does for std::string.
> Neither does Pascal.
Original Pascal didn't, but I think most modern Pascals do (e.g. Object Pascal known from Delphi).
I was referring to Access.
>Original Pascal didn't, but I think most modern Pascals do
I'm pretty sure concat() was used in THINK and Metrowerks Pascal in the 90s.
VBA (and VB in general) overloads "+" for strings in addition to providing "&", the difference being mostly in what gets converted to what in various situations: https://docs.microsoft.com/en-us/office/vba/language/referen...
Accepting both a numerical user id and a username string is a very valid scenario. However, I believe you should handle that by explicitly trying to parse it as a number, not hoping that automatic coercion will do the right thing. It's just a footgun waiting to go off.
This is why many people recommend questioning the use of basic or naked types. Details vary between the language, but this should be a type UserID with a UserID::from(String) and maybe a UserID::from_symbolic_name(String) and everything but some edge code should work on UserIDs. This makes code easier to read, too.
"5" == 5
"⑤" == 5
"⑸" == 5
"⁵" == 5
"𝟝" == 5
"𝟓" == 5
"𝟻" == 5
"V" == 5
"Ⅴ" == 5
"𝍸" == 5 # https://unicode-table.com/en/1D378/
"𐄋" == 5 # https://unicode-table.com/en/1010B/
"৫" == 5 # https://unicode-table.com/en/09EB/
If you think the first should work, you should justify why the others shouldn't.https://theasciicode.com.ar/ascii-printable-characters/numbe...
have you all ripped down your ASCII posters already
5 == "5" -- false
5 + "5" -- 10
5 .. "5" -- 55
It's just dynamic enough, none of the conversions are surprising and they're frequently helpful.5 and "5" are different objects, one is a number and one is a string, they shouldn't compare for equality.
+ in Lua is only addition, you can't add a string to anything, so if that string can be a number, that's obviously what was intended.
Similarly, .. is only concatenation, so if you're concatenating a number you clearly want it to be a string.
It works great in practice. How is this better than anything which languages that overload addition and concatenation can offer?
Maybe that would be a reasonable assumption in a world where programmers were omniscient and never made mistakes. In practice these "do what I mean" rules just tend to make it easier for poorly-specified programs to produce nonsense without signaling that anything is wrong. If your program intends to add two numbers and one of the inputs turns out to be a string then someone gave you bad input; the right solution to that is to report an error, not continue with bad data. Perhaps it's only an accident that the string happens to look like a number this time. Maybe it wasn't intended to be a number at all, and some parameters got swapped or an input file was corrupted.
Also, that rule is too broad. The string "5" could be treated as a number, sure, but so could "five" or "⑨" or "¾". So why is "five" + "⑨¾" an error and not 14.75 (or even "14¾")? If you want to accept number-like strings as numbers, fine, but there should be an explicit process where the programmer defines exactly what form the input can take and how to perform the conversion to a number. Implicit conversions skip that step and expose the internal defaults of the language to the end user.
It is conventional in PHP and JavaScript to use === unless there is some reason not to.
"" ==