Thanks for the writeup! It's always great to hear another prospective and this post gives some fairly strong indications of where our docs could be clearer.
One distinction I'd like to draw re: == and eqv is between the "the behaviors of equality operators" and "the richness of the type system". As you point out, in JavaScript, 1, Math.cos(0) and 1.0 are all === – and this makes perfect sense, because the typeof each one is number and the value of each one is 1; thus, they really are equal, both in type and value.
In contrast, in Raku, 1 is an Int, cos(0) is a Num (i.e., a floating point number) and 1.0 is a Rat (a rational number). And that distinction (sometimes!) matters, either for performance or correctness reasons, and I'm glad to have access to eqv. (For example, with floating point rounding errors: .1 × 3 == .3 multiplies a Rat and thus returns True; .1e0 × 3 == .3 multiplies a Num/float and returns False – the way most other languages behave.) In other cases, the distinction between 1 doesn't matter – I just care how many of something there is – and I'm glad to have ==.
Maybe it's because of the time I've spent with Rust and other languages that care more about their types, but I have a strong preference for equality operators that are explicit about their type conversions. Ruby's decision to have 1 and 1.0 be == even though they're different classes strikes me as a misfeature: in every other case== tests for object equality, but in this one, special case it does something different. Though I totally understand that it's an area where people disagree :)
Every language does 1 == 1.0 numerically, so Ruby isn't unusual here, Raku's eqv is the weird one out.
But mainly if you have a bunch of numbers of different types in Ruby, it is immediately obvious which number is what type, as they all print as different:
And I think pretty much all other languages where there are ints and floats and other types. If there are multiple number types, they appear as different.
In Raku they appear identical as 1, while eqv treats them as different. I don't think this is a good design.
Well, "print as" is also a complex topic :) In Raku, the .gist method (which is used by say etc) prints both 1.0 and 1 as 1, but the .raku method (which prints the debug representation) prints 1.0 and 1. This feels correct to me – again, maybe because of time spent with Rust, which makes exactly the same choice.
Again with Rust, not only does 1 == 1.0 not return true, it doesn't even compile (without an explicit cast). So, from a certain point of view, Raku's behavior represents something of a compromise between the dynamic behavior of a language like Ruby and the type-system-enforced guarantees of a more static language.
And, really, Raku falls somewhere between those two extremes quite frequently. You noted Raku's clear Perl legacy, and that's definitely a big part of the linage. But Raku's DNA also owes a surprisingly large amount to Haskell due to a large number of Haskellers involved in the early design process (or so I've heard/read – I wasn't involved that early).
Thanks for the writeup! It's always great to hear another prospective and this post gives some fairly strong indications of where our docs could be clearer.
One distinction I'd like to draw re:
==
andeqv
is between the "the behaviors of equality operators" and "the richness of the type system". As you point out, in JavaScript,1
,Math.cos(0)
and1.0
are all===
– and this makes perfect sense, because thetypeof
each one isnumber
and the value of each one is 1; thus, they really are equal, both in type and value.In contrast, in Raku,
1
is anInt
,cos(0)
is aNum
(i.e., a floating point number) and1.0
is aRat
(a rational number). And that distinction (sometimes!) matters, either for performance or correctness reasons, and I'm glad to have access toeqv
. (For example, with floating point rounding errors:.1 × 3 == .3
multiplies aRat
and thus returnsTrue
;.1e0 × 3 == .3
multiplies aNum
/float and returnsFalse
– the way most other languages behave.) In other cases, the distinction between1
doesn't matter – I just care how many of something there is – and I'm glad to have==
.Maybe it's because of the time I've spent with Rust and other languages that care more about their types, but I have a strong preference for equality operators that are explicit about their type conversions. Ruby's decision to have
1
and1.0
be==
even though they're different classes strikes me as a misfeature: in every other case==
tests for object equality, but in this one, special case it does something different. Though I totally understand that it's an area where people disagree :)Every language does 1 == 1.0 numerically, so Ruby isn't unusual here, Raku's eqv is the weird one out.
But mainly if you have a bunch of numbers of different types in Ruby, it is immediately obvious which number is what type, as they all print as different:
Same with Python:
And I think pretty much all other languages where there are ints and floats and other types. If there are multiple number types, they appear as different.
In Raku they appear identical as 1, while eqv treats them as different. I don't think this is a good design.
Well, "print as" is also a complex topic :) In Raku, the
.gist
method (which is used bysay
etc) prints both1.0
and1
as1
, but the.raku
method (which prints the debug representation) prints1.0
and1
. This feels correct to me – again, maybe because of time spent with Rust, which makes exactly the same choice.Oh, and re:
Again with Rust, not only does
1 == 1.0
not return true, it doesn't even compile (without an explicit cast). So, from a certain point of view, Raku's behavior represents something of a compromise between the dynamic behavior of a language like Ruby and the type-system-enforced guarantees of a more static language.And, really, Raku falls somewhere between those two extremes quite frequently. You noted Raku's clear Perl legacy, and that's definitely a big part of the linage. But Raku's DNA also owes a surprisingly large amount to Haskell due to a large number of Haskellers involved in the early design process (or so I've heard/read – I wasn't involved that early).
Haskell makes the same choices as Ruby and Python (and pretty much every other language):