Due to my ex-Perl background, this episode is very much "how much Raku fixes Perl issues" story, that's why I'm focusing on this.
It's sort of true that eqv and lack of behind-the-scenes variable conversions are a huge improvement over Perl already. But are the fixes going far enough?
For example these are three eqv-different objects, even though they print the same, == each other, eq each other, JSON-equal each other, and in a typical language with universal == they'd all be equal to each other (and are also pretty much indistinguishable in Perl):
> [cos(0), 1, 1.0]
[1 1 1]
So using eqv as "universal ==" would require a lot of care to be paid to such invisible differences - something you don't need to do if you code in Ruby or Python or such, where == is completely predictable.
Another issue, and that comes up a lot. Now that we have eqv, can we have a Hash (or similar structure) that keys by eqv, just like Hashes in Ruby or dicts in Python work?
This doesn't work, and I'm not seeing anything that would work instead:
my %map;
my $x = 2;
my $y = 5;
%map{[$x, $y]} = 10; // a single key [2, 5], or anything eqv to it
I know of Perl workarounds like stringifying keys or (for this simple case not in general) double-nesting Hashes, but they're drastically inferior to just being able to use arbitrary keys in Ruby (or at least arbitrary immutable keys in Python, which is close enough). Raku seems to be still suffering from Perl issues here.
JavaScript has same Perl legacy, and it used to suffer from the same issue, with only string-keyed hashes, and introduced ES6 Maps as a fix, so now we can use any objects as keys. I couldn't find any Raku equivalent.
As for sin vs .sin, I'm not sure why I missed that. You're right that both forms work.
Due to my ex-Perl background, this episode is very much "how much Raku fixes Perl issues" story, that's why I'm focusing on this.
Understood. But Raku borrowed a lot of ideas from many programming languages. And hopefully only the good things :-)
something you don't need to do if you code in Ruby or Python or such, where == is completely predictable
Well, I'd argue that ==is completely predictable: it does a numerical comparison. I'd argue that a "universal ==" actually does more harm than good, as it may sweep potentially important differences "under the carpet" as it were.
Raku seems to be still suffering from Perl issues here.
You seemed to have missed object hashes. In short, you can specify a constraint to be applied on the keys of a hash. The default is Str(), which is short for Str(Any), which means: accept Any object (all classes except Junction and Mu inherit from Any) object and coerce it to Str.
An example of using just integer keys, would be:
my %hash{ Int };
%hash{ 42 } = "foo"; # ok
%hash{"42"} = "bar"; # NOT ok
If you want it to coerce to an Int, you can use Int() as the type:
my %hash{ Int() };
%hash{ 42 } = "foo"; # ok
%hash{"42"} = "bar"; # also ok
Now, when it comes to using lists as a key, the situation is a little more complex. You can use a value type as a key in an object hash. An immutable Set is an example of a value type:
my $s1 = Set.new(1,2,3);
my $s2 = Set.new(3,2,1);
my %hash{Set};
%hash{ $s1 } = 42;
say %hash{ $s2 }; # 42
Now, Lists and Arrays are not value types: Arrays because they are mutable, and Lists because they can contain mutable elements:
my ($a, $b) = 42, 137; # LHS is a List with mutable elements
One approach to fix this for now, is the Tuple class. Discussions have been had about making Lists value types, but the above idiom is pretty pervasive in the Raku ecosystem and "darkpan", so not easy to fix. Perhaps a future version of Raku will allow for this to work:
my [$a,$b] = 42, 137;
to indicate a list with mutable elements, thereby freeing up () for immutable value type lists. The future will tell. Fortunately, Raku will allow such a change in syntax in a new language level: each compilation unit can indicate at which language level they wish to be compiled. This allows different semantics to be applied, while keeping interoperability between code of different language versions.
In short: the Raku Programming Language has "change" built into it, to make it a language of choice for long-living code bases. And which code base doesn't live longer than intended :-)
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).
Due to my ex-Perl background, this episode is very much "how much Raku fixes Perl issues" story, that's why I'm focusing on this.
It's sort of true that
eqv
and lack of behind-the-scenes variable conversions are a huge improvement over Perl already. But are the fixes going far enough?For example these are three
eqv
-different objects, even though they print the same,==
each other,eq
each other, JSON-equal each other, and in a typical language with universal == they'd all be equal to each other (and are also pretty much indistinguishable in Perl):So using eqv as "universal ==" would require a lot of care to be paid to such invisible differences - something you don't need to do if you code in Ruby or Python or such, where == is completely predictable.
Another issue, and that comes up a lot. Now that we have eqv, can we have a Hash (or similar structure) that keys by eqv, just like Hashes in Ruby or dicts in Python work?
This doesn't work, and I'm not seeing anything that would work instead:
I know of Perl workarounds like stringifying keys or (for this simple case not in general) double-nesting Hashes, but they're drastically inferior to just being able to use arbitrary keys in Ruby (or at least arbitrary immutable keys in Python, which is close enough). Raku seems to be still suffering from Perl issues here.
JavaScript has same Perl legacy, and it used to suffer from the same issue, with only string-keyed hashes, and introduced ES6 Maps as a fix, so now we can use any objects as keys. I couldn't find any Raku equivalent.
As for sin vs .sin, I'm not sure why I missed that. You're right that both forms work.
Understood. But Raku borrowed a lot of ideas from many programming languages. And hopefully only the good things :-)
Well, I'd argue that
==
is completely predictable: it does a numerical comparison. I'd argue that a "universal ==" actually does more harm than good, as it may sweep potentially important differences "under the carpet" as it were.You seemed to have missed object hashes. In short, you can specify a constraint to be applied on the keys of a hash. The default is
Str()
, which is short forStr(Any)
, which means: acceptAny
object (all classes exceptJunction
andMu
inherit fromAny
) object and coerce it toStr
.An example of using just integer keys, would be:
If you want it to coerce to an
Int
, you can useInt()
as the type:Now, when it comes to using lists as a key, the situation is a little more complex. You can use a value type as a key in an object hash. An immutable
Set
is an example of a value type:Now, Lists and Arrays are not value types: Arrays because they are mutable, and Lists because they can contain mutable elements:
One approach to fix this for now, is the Tuple class. Discussions have been had about making Lists value types, but the above idiom is pretty pervasive in the Raku ecosystem and "darkpan", so not easy to fix. Perhaps a future version of Raku will allow for this to work:
to indicate a list with mutable elements, thereby freeing up
()
for immutable value type lists. The future will tell. Fortunately, Raku will allow such a change in syntax in a new language level: each compilation unit can indicate at which language level they wish to be compiled. This allows different semantics to be applied, while keeping interoperability between code of different language versions.In short: the Raku Programming Language has "change" built into it, to make it a language of choice for long-living code bases. And which code base doesn't live longer than intended :-)
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):