Thank you! This is a very well put article and I agree with most of what you say. I opened a forum discussion about this to gather some ideas: forum.crystal-lang.org/t/fair-crit... . I don't think we can change things due to Crystal 1.0 backwards compatibility promise, but we can think about these things for 2.0, and maybe allow at least matching a Char against a regex.
I'd not count any of the "Better C++" languages as modern, even if they were released yesterday. "Better C++" languages all intentionally sacrifice productivity for other goals like performance (just how many string types Rust has? feels like it's at least 10).
My list would be more like (latest major version) Ruby, Python, JavaScript, Raku etc. I checked a bunch of what I considered modern languages, and Julia and Crystal seem to be the only ones with a separate Char type.
Anyway, what do you think a false positive rate would be if Crystal had a warning for statically type-impossible == or =~? I think it's relatively safe with a more traditional type system, but maybe what Crystal is doing makes this impossible.
Given that Crystal has union types it's essentially impossible to make == and =~ type safe. For example, say we want to restrict comparing numbers against numbers only, never strings. But now you have a variable of type Int32 | String. You want to check if that's equal to "hello". The compiler won't let you write that program because it will say "I can't compare Int32 with String". You'd have to write something like x.is_a?(String) && x == "hello". So it will lead to incredibly verbose code.
The same argument applies to letting any type only be comparable to some other type.
With =~ or === we could maybe make it more strict, not sure. But for example if you have a variable of type String | Nil and we'd like to disallow =~ for nil (it will never match) then you'd have to write !value.nil? && value =~ "...".
In the end, this was just a tradeoff between verboseness vs. how common it is to fall into this trap. If you know that String#[Int32] returns a Char, and that you can't compare Char with Regex, then you won't do it. So I think it's just a matter of how much exposure you had to the language before.
(every language has some of this, it's inevitable)
I didn't mean it as a type check, I meant it more as a linter warning if == is statically impossible. A few languages and linters have some sort of Condition is always false warning.
In Crystal's case it would be based on type overlap. So the idea is that ARGV[0] == nil would trigger this linter rule (String and Nil don't overlap, so it's always false), but ARGV[0]? == nil wouldn't (String|Nil and Nil overlap).
I'm almost sure that there will be a lot of false positives, based on how the compiler and language works (or put another way: the type system.) Maybe there's a rule for this already in Ameba (a popular Crystal linter)
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Thank you! This is a very well put article and I agree with most of what you say. I opened a forum discussion about this to gather some ideas: forum.crystal-lang.org/t/fair-crit... . I don't think we can change things due to Crystal 1.0 backwards compatibility promise, but we can think about these things for 2.0, and maybe allow at least matching a Char against a regex.
I'd not count any of the "Better C++" languages as modern, even if they were released yesterday. "Better C++" languages all intentionally sacrifice productivity for other goals like performance (just how many string types Rust has? feels like it's at least 10).
My list would be more like (latest major version) Ruby, Python, JavaScript, Raku etc. I checked a bunch of what I considered modern languages, and Julia and Crystal seem to be the only ones with a separate Char type.
Anyway, what do you think a false positive rate would be if Crystal had a warning for statically type-impossible
==
or=~
? I think it's relatively safe with a more traditional type system, but maybe what Crystal is doing makes this impossible.Given that Crystal has union types it's essentially impossible to make
==
and=~
type safe. For example, say we want to restrict comparing numbers against numbers only, never strings. But now you have a variable of typeInt32 | String
. You want to check if that's equal to "hello". The compiler won't let you write that program because it will say "I can't compare Int32 with String". You'd have to write something likex.is_a?(String) && x == "hello"
. So it will lead to incredibly verbose code.The same argument applies to letting any type only be comparable to some other type.
With
=~
or===
we could maybe make it more strict, not sure. But for example if you have a variable of typeString | Nil
and we'd like to disallow=~
fornil
(it will never match) then you'd have to write!value.nil? && value =~ "..."
.In the end, this was just a tradeoff between verboseness vs. how common it is to fall into this trap. If you know that
String#[Int32]
returns a Char, and that you can't compare Char with Regex, then you won't do it. So I think it's just a matter of how much exposure you had to the language before.(every language has some of this, it's inevitable)
I didn't mean it as a type check, I meant it more as a linter warning if
==
is statically impossible. A few languages and linters have some sort ofCondition is always false
warning.In Crystal's case it would be based on type overlap. So the idea is that
ARGV[0] == nil
would trigger this linter rule (String
andNil
don't overlap, so it's alwaysfalse
), butARGV[0]? == nil
wouldn't (String|Nil
andNil
overlap).I'm not sure how practical that would be.
I'm almost sure that there will be a lot of false positives, based on how the compiler and language works (or put another way: the type system.) Maybe there's a rule for this already in Ameba (a popular Crystal linter)