`Enumerable(T).includes?` takes object of any type

Hi folks!

I am a little confused by the includes? method as to why it does not have a type check at its parameter (def includes?(obj) : Bool as opposed to def includes?(obj : T) : Bool)

For example, code like this which will always return false is able to compile:

["hello", "world"].includes?(1)

Is there any reason for this?

Because includes? checking existence by using overloadable operator == which works beyond a single type.

1 Like

I had a feeling that would be the case. But tbh, I created a bug in my code due to this, which is what made me realize there was no type-checking happening on this method.

But what I don’t understand is, rather than making the type so generic, wouldn’t it be good if a dev–who wanted to overload the == operator to extend to other types–could just define their own overloads

def includes?(t1 : Type1)
  # ...

def includes?(t2 : Type2)
  # ...

This wouldn’t really work with generic collections.
A restriction to T would be possible, though. Here has been a similar proposal for Hash#[]: Enforce hash key types at compile time by jgaskins · Pull Request #8893 · crystal-lang/crystal · GitHub

This is essentially an instance of the conflict between convenience of use and type safety.
I’d rather chose type safety, but we’re not there currently.

Perhaps it would be possible to consider having two flavours of such collection methods with varying strictness.


Thank you for the explanation, @straight-shoota !

I admire the flexibility that it gives to compare different types. It is even prevalent in Java if I’m not mistaken. Possibly a common feature in OOP languages in general. However, since I rely heavily on types and rules enforced by them during compilation, I probably won’t be needing this feature (even in the future) :sweat_smile:. Perhaps, it might be a good idea to have two flavours indeed :slight_smile:

For now I’ll polyfill my code:

module Enumerable(T)
  def includes_t?(obj : T)
    includes? obj