dart-lang / language

Design of the Dart language
Other
2.67k stars 205 forks source link

Should abstract Enum have `values`? #1996

Open ltOgt opened 2 years ago

ltOgt commented 2 years ago

I often wan't to cycle through enums. But I don't think it is possible to write e.g. the following in a way that is re-usable across enums

enum MyEnum { a, b, c }

void main(List<String> args) {
  MyEnum mE = MyEnum.a;
  me.next == MyEnum.b;
}

extension MyEnumX on MyEnum {
  MyEnum get next => nextMyEnum(this);
}

MyEnum nextMyEnum(MyEnum value) => //
    MyEnum.values[value.index + 1 % MyEnum.values.length];

The abstract 'Enum' looks like this:

/// This class is implemented by all types and values
/// introduced using an `enum` declaration.
abstract class Enum {
  /// ...
  /// This is also the index of the value in the
  /// enumerated type's static `values` list.
  int get index;
}

If the abstract class defined a getter for values for example, one could do:

E nextMyEnum<E extends Enum>(E value) => 
    value.values[value.index + 1 % value.values.length];

Also related: https://stackoverflow.com/a/60459896/6862386

lrhn commented 2 years ago

Adding values as an instance member is new (and breaking, you can't also have a static values member). It's not a particularly good fit, because while you can often do something from one just enum value, you also (just as frequently in my experience) want to do something without any enum value to being with.

Say, implement an EnumSet<T extends Enum>, which will be more efficient if you know the number of enum values when the EnumSet is created, before any element is added.

I'd much rather have a static helper on Enum, like static List<T> valuesOf<T extends Enum>(); which "magically" and semi-reflection-like extracts the values list from the enum type. Then your code would be:

E nextMyEnum<E extends Enum>(E value) => 
    value.values[(value.index + 1) % Enum.valuesOf<E>().length];
ltOgt commented 2 years ago

Oh yeah that would be great

lrhn commented 2 years ago

The con of an Enums class is that every one of the methods could just be static functions on Enum instead. The class is unnecessary.

For static methods, the con is that extension methods are generally better that static helpers. That's why name is already an extension getter on Enum instances. The index getter could be an extension getter (q.v. #1995 discussion).

The List<T> valuesOf<T extends Enum>() static method is special in that it really is a kind of reflection. It's not something that can be implemented in plain Dart (not unless we have a global mapping from enum type to values).

It also needs some more design because you can define (with the proposed enum enhancements)

enum Foo<T> {
  foo1<int>(),
  foo2<double>(),
  foo3<String>()
}

and if you then do Enum.valuesOf<Foo<num>>(), what should it return? It can't return const[Foo.foo1, Foo.foo2, Foo.foo3] because that doesn't have type List<Foo<num>>, just List<Foo<Object?>>.

Would it dynamically create a list of the values that satisfy that type? Like:

  static List<T> valuesOf<T extends Enum>() {
    List<Enum> values = _magicalLookupValuesConstant<T>();
    if (values is List<T>) return values;
    return List<T>.unmodifiable(values.whereType<T>());
  }

It's non-trivial, sadly :(

igotfr commented 2 years ago

Dart enums are amenics and force us to use class

Rust Enums

pub enum Option<T> {
    None,
    Some(T)
}
enum IpAddr {
    V4(String),
    V6(String)
}
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String)
}
igotfr commented 2 years ago

Enums in Rust mean something totally different from whatever we discuss here. In Rust, the concept called "enum" applies to algebraic data types, not values. There's one version of rust enum that supports values (in the same sense as the proposed dart enums), but the values should be all integers. Details

Rust supports the 2 simultaneously, I don't need change version:

enum Foo {
    Bar,            // 0
    Baz = 123,      // 123
    Quux,           // 124
}

let baz_discriminant = Foo::Baz as u32;
assert_eq!(baz_discriminant, 123);
igotfr commented 2 years ago

the ADT enum could be another name, Haskell uses data and V uses type

igotfr commented 2 years ago

By "version", I don't mean "Rust version". It's a "flavor" of enums (why they use the same word for 2 different concepts is beyond me). In the "value" flavor, the said "value" is integer only. The current proposal is about porting java enums - something that is found in java only (no other language I'm aware of copied the concept). I'm wondering why.

EDIT: maybe this thing has something to do with it? Expires in 2024.

it's has to do, but is very verbose and unnecessary, I've never seen it anywhere. The variations I know are Haskell's data, Rust's enum, and V's type (sumtype)

https://github.com/vlang/v/blob/master/doc/docs.md#sum-types