Crystal: [RFC] `Enum#is?` instead of custom enum member methods

Created on 27 Jul 2019  路  11Comments  路  Source: crystal-lang/crystal

Right now if we have:

enum Color
  Red
  Green
  Blue
end

a bunch of methods are generated for the enum members so you can do:

color = Color::Red
color.red? # => true
color.green? # => true
color.blue? # => true

This was added to avoid having to write:

color == Color::Red
# vs.
color.red? # much easier to read and write!

Now that we have automatic casting of symbols into enums we can introduce an is? method to do the same. It's just a bit more typing:

color.is?(:red)

The advantages of doing this are:

  • the compiler doesn't need to generate these methods anymore
  • there's less magic involved (just the autocasting one)
  • you can pass :red or :Red if you prefer the latter

Side note

Because == and === is defined on all types you can't really do color == :red. Well, you can, but it will match the most general overload which returns false. No autocasting can be involved.

This is probably not very intuitive so I propose to do one of these things:

  • Add Enum#==(Symbol) and Symbol#==(Enum) that work by checking the downcase string representation of the enum and symbol.
  • Raise a compilation error when comparing enums to symbols and suggest using is? instead

I'm not sure which one I prefer. Maybe the second one is better because it guarantees efficient code... plus it would be uncommon to have code that deals with enums and symbols at the same time.

draft

Most helpful comment

I'd prefer to leave this as-is. Is there really a problem, in practice, with the current way?

All 11 comments

Looks good, and what about the opposite, would it be color.not?(:red)?
(I also secretly hope to change not_nil! to #not!(Nil) one day :smile: )

I'm stupid, we can use !color.is?(:red) :sweat_smile: , unless if we want a raising color.not!(:red)

I don't understand, though, wouldn't it just work like this?

enum A
  X

  def is?(other : A)
    self == other
  end
end

p! A::X.is?(:x)  # => true

Then it's just a bit strange that it doesn't work specifically for operators, though it's the same approach:

enum A
  X

  def ==(other : A)
    super
  end
end

p! A::X.==(:x)  # => false

Wouldn't need "checking the downcase string representation" then, if we can just rely on #6074

Aren't we loosing compile-time safety with this approach?

The problem is that a symbol doesn't match A and so the supertypes are looked up and == from Object is found and so it gives false, not leaving a chance for autocasting to kick off.

I'd really like enum vs symbol comparison to work somehow... the only way I can think of it right now is by harcoding the logic in the compiler.

a symbol doesn't match A

Yeah then what is it doing in my first example which actually works?

Yeah then what is it doing in my first example which actually works?

Which one?

I'd prefer to leave this as-is. Is there really a problem, in practice, with the current way?

One issue is #7084: Underscore representations of enum members can collide (Foo and FOO) for example. This wouldn't technically solve this, because the symbol representation is similarly affected. But it could be an option to have the symbol equal the constant name, in which #is? would be maybe easier to use than methods with uppercase first character.

@straight-shoota than having both would be preferable since the query methods are type safe whereas #is? check is not.

Why wouldn't #is? be type safe. I'm assuming it is.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nabeelomer picture nabeelomer  路  3Comments

cjgajard picture cjgajard  路  3Comments

jhass picture jhass  路  3Comments

oprypin picture oprypin  路  3Comments

lbguilherme picture lbguilherme  路  3Comments