Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

case enum #285

Closed
asterite opened this issue Dec 2, 2014 · 13 comments
Closed

case enum #285

asterite opened this issue Dec 2, 2014 · 13 comments

Comments

@asterite
Copy link
Member

asterite commented Dec 2, 2014

We have enums and you can use case with them:

enum Color
  Red
  Green
  Blue
end

color = Color::Red
a = case color
    when Color::Red
      1
    when Color::Green
      2
    when Color::Blue
      3
    end

Enums are great because, unlike symbols, they are type-safe. They are like a group of limited and documented symbols. You can't accidentally misspell them. And you can use them as restrictions.

However, the above case has a few issues:

  1. You have to repeat the enum's name in every branch.
  2. a's type is nilable, because there's an implicit else part with nil. Ideally the compiler should be smart and know there are no other possible values. This is partly because the compiler blindly transforms the case into a series of if-elseif, but also, because of how our type inference works, color might eventually get another type.
  3. There's no way to say that we want to cover all cases and, later, when we add another enum value, let the compiler know that we want a compiler error until we cover all cases again. This would be somewhat similar to D's final switch.

I'm thinking about having something like this:

# But with a less ugly syntax...
case color as enum Color
when Red
  ...
when Green
  ...
when Blue
  ...
end

Possibly with a modifier to make it final.

@mverzilli
Copy link

What about:

case color : Color
when Red
  ...
when Green
  ...
when Blue
  ...
end

@asterite
Copy link
Member Author

asterite commented Dec 2, 2014

@mverzilli I like that syntax. In fact, we can use it beyond enums to avoid repeating the namespace. So we could do this:

class Foo
  A = 1
  B = 2
  C = 3
end

case foo : Foo
when A
  ...
when B
  ...
when C
  ...
end

To avoid having to write Foo::A, Foo::B and Foo:C. Then the case foo : Foo syntax would mean "using this as a namespace". And for enums we can do a little more.

I'm not totally convinced about it, though, because the colon is starting to have many meanings, but it looks short and readable.

@vendethiel
Copy link
Contributor

Uhhh.. Having a "use namespace" as postfix-colon-in-case seems weird to me :)

@mverzilli
Copy link

Actually, I thought of case foo : Foo as a type annotation, meaning "foo is a Foo".

Then for enums one can assume when clauses refer to Foo's possible values. I agree that the extrapolation of this idea to classes may feel weird.

I kind of borrowed the idea from Scala and its case classes, but of course that's far from being a simple switch over enum values :).

@papamarkou
Copy link
Contributor

I don't know the semantics of Crystal so deeply yet to know if this can be implemented, yet I foresee at a high level a rising pattern of implicit type annotations, see also the relevant issue #284, where properties can be "type-annotated" via a single colon too (not sure if they can actually, but it would be amazing to bring all these issues together under a common type annotation single colon) :)

@bcardiff
Copy link
Member

bcardiff commented Dec 2, 2014

  1. we don't need additional syntax to avoid requiring the Enum name. We can just support that if the case is over a enum type, that type is used for the constant lookup on the nested whens.

  2. for a syntax that enforce that all cases are covered what about:

case every color
when Red
  1
when Green
  2
end

?

@vendethiel
Copy link
Contributor

This conflicts with implicit calls, doesn't it? It's currently case every(color).
Also, you shouldn't have to explicitly ask the compiler to do exhaustiveness check – it should do it by default (if it can).

@bcardiff
Copy link
Member

bcardiff commented Dec 2, 2014

@vendethiel unless every is a keyword :-P.
the only scenario that the exhaustive check should be run automatically is in the Enum case or (sub)type checking (maybe).

@vendethiel
Copy link
Contributor

enum case. We can't do subtype checking. Would love to see enums being used as ADTs :)

@asterite
Copy link
Member Author

asterite commented Dec 2, 2014

What @bcardiff says is maybe the best thing, because the compiler tries to be as smart as possible based on the type of the variable. It's the hardest thing to do in the compiler, but programmers will be happier :-)

@bcardiff
Copy link
Member

bcardiff commented Dec 9, 2014

What about adding other control sequence: match that will do the exhaustive match. Initially for enum, but can be used later for pattern matching eventually. With tuples or something else later, maybe introducing new variables in the capture. don't know. It should not have a default.

@asterite
Copy link
Member Author

Now that we added enum query methods, it can be done like this:

color = Color::Red
a = case color
    when .red?
      1
    when .green?
      2
    when .blue?
      3
    end

@timjs
Copy link

timjs commented Apr 26, 2017

Could this be documented? The book shows an example like this, but there isn't a clue that this is automatically derived.

Also, after this discussion I'd expect an compile error, but running this code compiles fine and prints 1 : (Int32 | Nil) on Crystal v0.22.0.

enum Color
  Red
  Green
  Blue
end

color = Color::Red
x = case color
  when .red?
    1
  when .green?
    2
  end

printf("%s : %s\n", x, typeof(x))

(Related: #1846)

veelenga added a commit to veelenga/lua.cr that referenced this issue Apr 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants