eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
398 stars 62 forks source link

singleton constructors #4235

Closed CeylonMigrationBot closed 8 years ago

CeylonMigrationBot commented 9 years ago

[@gavinking] In #3902 the notion of singleton constructors arose. That is, the ability of a class with constructors (i.e. without an initializer parameter list) to declare singleton instances whose bodies work like a constructor and initialize the state of the instance. For example:

class Point {
    shared Float x;
    shared Float y;
    shared new Point(Float x, Float y) {
        this.x = x;
        this.y = y;
    }
    shared case origin {
        x = 0.0;
        y = 0.0;
    }
}

A singleton instance reference would look like Point.origin. One motivation for this feature is that in conjunction with #4231 it would give us an alternative, streamlined way to do Java-style enums.

Issue: the interpretation as an enum value is what's motivating the use of case as the keyword here. Even though case doesn't feel quite perfect to me, value and object would both be ambiguous here, and new doesn't read right.

[Migrated from ceylon/ceylon-spec#1129] [Closed at 2015-06-20 20:04:25]

CeylonMigrationBot commented 9 years ago

[@gavinking] Today I made a start on this and it's basically working. Missing things include:

  1. Delegation to constructors e.g. new origin extends Point(0.0,0.0) {}
  2. Restrictions around classes with explicit of clauses and singleton constructors.
  3. Addition of a constructor makes singleton instances non-exhaustive.

I've done this in a way that should make it really very easy for the backend to start supporting (singleton instances are just nested objects.)

CeylonMigrationBot commented 9 years ago

[@jvasileff] Java enums allow you to override methods (possibly abstract in the parent class), which I think is a pretty nice feature.

So it might be nice to allow cases to be defined outside of the body of the class, and extend it.

CeylonMigrationBot commented 9 years ago

[@gavinking] @jvasileff we've been through this. Java's system overcompensates for the lack of first-class functions in the language (prior to Java 8). In Ceylon we can write the same thing using assignment:

class Foo {
    shared void accept(Float x);
    shared new New1() {
        accept(Float x) => print(x);
    }
    shared new New2(void accept(Float x)) {
        this.accept = accept;
    }
}

Or, with a singleton constructor:

class Foo {
    shared void accept(Float x);
    shared new instance {
        accept(Float x) => noop(x);
    }
}
CeylonMigrationBot commented 9 years ago

[@gavinking]

So it might be nice to allow cases to be defined outside of the body of the class, and extend it.

Note: we already have that possibility with what exists in Ceylon today with the of clause. We don't need a second way to do it.

CeylonMigrationBot commented 9 years ago

[@gavinking] Well, OK, fine, I admit it might be nice to let you write:

class Foo {
    void accept(Float x);
    shared new New() {
        actual void accept(Float x) {
            print(x);
        }
    }
}

It's not overriding precisely, since New is not a subclass, it's just an alternative syntax for assignment. But it's quite regular. (Notice that accept() doesn't need to be shared for this, and nor is it formal.)

CeylonMigrationBot commented 9 years ago

[@jvasileff] Ok, yes, those look like nice options.

CeylonMigrationBot commented 9 years ago

[@gavinking] OK, I now have a much cleaner implementation of this in the 1129 branch. Basically, a singleton constructor is now represented in the model as a Constructor + a Value.

If anyone has time to work on backend support for this, go ahead, it should be straightforward, you just need to treat it like a regular Constructor, but in addition generating a static accessor for a singleton instance. Should be very easy to get it working.

For now, we don't need to support delegation to constructors.

CeylonMigrationBot commented 9 years ago

[@gavinking] So this has worked out nicely, but there is one thing which bothers me a bit. Previously, with of, defining an enumerated type was a totally explicit thing. The client knew for sure that you intended for the list of things in the of clause to be exhaustive.

With what I have right now for singleton constructors that's not true. Perhaps I plan to add a new instance, or think I might. How do I indicate that clients?

One option would be to abuse final and allow switches on the enumerated instances of a class explicitly marked final. That's not really precisely what final means, but it's quite close. In practice I think it would work out fairly well, but still, it makes me uncomfortable.

A second option would be to introduce an enumerated annotation for classes which can be switched. I don't like that this is so irregular compared to the current mechanism.

A third, much more regular option would be to make you use the of clause to list the constructors explicitly:

shared class Bool of true | false {
    shared new true {}
    shared new false {}
}

I think I prefer this since it makes the connection to what we already have today. It's just much more consistent.

CeylonMigrationBot commented 9 years ago

[@gavinking] Perhaps let you abbreviate it like this:

shared class Bool of ... {
    shared new true {}
    shared new false {}
}

Hurmph.

CeylonMigrationBot commented 9 years ago

[@lucaswerkmeister] I like the third, explicit option (without the ..., which look really weird).

CeylonMigrationBot commented 9 years ago

[@quintesse] The last part seems unnecessary to me, it's isn't that much work to repeat the names.

CeylonMigrationBot commented 9 years ago

[@tombentley]

it's isn't that much work to repeat the names.

Well it depends how many there are and how long they are. Look at ceylon.unicode::Directionality for an example where the repetition would be annoying.

CeylonMigrationBot commented 9 years ago

[@quintesse]

Look at ceylon.unicode::Directionality for an example where the repetition would be annoying.

Not sure, as I see it we are already repeating those names there right now as well and personally I don't find them annoying. And like @lucaswerkmeister I find the of .. option weird looking.

CeylonMigrationBot commented 9 years ago

[@gavinking] Yes Tom is right I'm bothered by what ceylon.unicode looks like.

Sent from my iPhone

On 1 Jun 2015, at 2:14 pm, Tom Bentley notifications@github.com wrote:

it's isn't that much work to repeat the names.

Well it depends how many there are and how long they are. Look at ceylon.unicode::Directionality for an example where the repetition would be annoying.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@quintesse] Also to me the of ... option seems just like a cryptic version of the enumerated annotation.

CeylonMigrationBot commented 9 years ago

[@gavinking] So the broader question is: how useful are singleton constructors outside of the context of defining enums?

If we were to say a class can have either singleton or parameterized constructors but not both, then I would be OK with dropping the need to make it explicit. Because I think in that case we can just position this as being for enums and nothing else.

But if you can mix singletons and constructors in the same class, which feels quite useful to me, then I feel like it needs to be explicit because singletons no longer carry the expectation that they are for enums and nothing else.

Sent from my iPhone

On 1 Jun 2015, at 2:18 pm, Tako Schotanus notifications@github.com wrote:

Look at ceylon.unicode::Directionality for an example where the repetition would be annoying.

Not sure, as I see it we are already repeating those names there right now as well and personally I don't find them annoying. And like @lucaswerkmeister I find the of .. option weird looking.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@quintesse] And why exactly does the option of making the class final make you uncomfortable? It does seem like a simple solution.

CeylonMigrationBot commented 9 years ago

[@gavinking] Well final doesn't mean it can't have parameterized constructors. And it doesn't, strictly speaking, mean I'm not going to add any new constructors.

Sent from my iPhone

On 1 Jun 2015, at 2:42 pm, Tako Schotanus notifications@github.com wrote:

And why exactly does the option of making the class final make you uncomfortable? It does seem like a simple solution.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@quintesse]

Well final doesn't mean it can't have parameterized constructors

Sure, but I thought you found that useful so you wanted to keep that.

And it doesn't, strictly speaking, mean I'm not going to add any new constructors.

Well... right. But I'm not confident that people will use of because they are sure they'll never ever cross-my-heart-hope-to-die add new items either. In many cases it'll probably be used because they want nice switching behaviour.

CeylonMigrationBot commented 9 years ago

[@gavinking] Right the point is that if you want to let your clients switch on your constructors, I want you to explicitly indicate that by doing something that you would not do for any other motivation.

Because people, and it doesn't matter what I think of this practice, will start using this to just stick a list of values inside a wrapping namespace, without considering whether this is a closed list of values or not.

So I think it's gotta be explicit.

Sent from my iPhone

On 1 Jun 2015, at 2:59 pm, Tako Schotanus notifications@github.com wrote:

Well final doesn't mean it can't have parameterized constructors

Sure, but I thought you found that useful so you wanted to keep that.

And it doesn't, strictly speaking, mean I'm not going to add any new constructors.

Well... right. But I'm not confident that people will use of because they are sure they'll never ever cross-my-heart-hope-to-die add new items either. In many cases it will probably be added because you want nice switching behaviour.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@FroMage] Whatever syntax we add, remember to compare it against:

public enum Foo {
 Bar, Gee;
}

And:

public enum FamousNumbers {
 Answer(42), Beast(666);

 public final int number;

 FamousNumbers(int number){ this.number = number; }
}

For brevity. I know I will compare them ;)

CeylonMigrationBot commented 9 years ago

[@quintesse]

without considering whether this is a closed list of values or not.

Ok, so you want to make sure they know they're going to make a breaking change (with respect to backward compatibility) if they ever add a new value. Well then I'd vote for being explicit without weird of ... shorthands.

CeylonMigrationBot commented 9 years ago

[@FroMage] I mean, I'm not at all opposed to a new grammar for enum classes.

CeylonMigrationBot commented 9 years ago

[@gavinking] Well I think we're doing that and that's what this discussion is about. But brevity is not the only value at play here.

Sent from my iPhone

On 1 Jun 2015, at 3:11 pm, Stéphane Épardaud notifications@github.com wrote:

Whatever syntax we add, remember to compare it against:

public enum Foo { Bar, Gee; } And:

public enum FamousNumbers { Answer(42), Beast(666);

public final int number;

FamousNumbers(int number){ this.number = number; } } For brevity. I know I will compare them ;)

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@gavinking] Well I am against that. For the two enum classes I write every year, I don't need a whole new keyword.

The only reason I'm even considering this whole idea really is that it offers the potential to make several things easier all at once. A specialised enum syntax would be crippled in the sense that it addresses just one problem and doesn't compose together with other language constructs the way this proposal does.

Sent from my iPhone

On 1 Jun 2015, at 3:12 pm, Stéphane Épardaud notifications@github.com wrote:

I mean, I'm not at all opposed to a new grammar for enum classes.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@gavinking] And I'm not exaggerating here: in the past year I recall writing one enum in Java, that's 1 i.e. the additive identity. It was SiteVariance, FTR.

And for that I need a whole special language construct whose syntax I barely remember?

Sent from my iPhone

On 1 Jun 2015, at 3:12 pm, Stéphane Épardaud notifications@github.com wrote:

I mean, I'm not at all opposed to a new grammar for enum classes.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@FroMage] It is well known that you never write enums yourself, but we do. We have more than twenty enums in the compiler alone, and would have a lot more if we weren't so damn lazy with calls like foo(true, false, false, true) where we have a really high threshold of switching to sanity.

CeylonMigrationBot commented 9 years ago

[@tombentley]

that's 1 i.e. the additive identity.

I think you mean the multiplicative identity, the additive identity is zero.

CeylonMigrationBot commented 9 years ago

[@FroMage] Remember you're not designing a language just for yourself ;)

CeylonMigrationBot commented 9 years ago

[@gavinking] Hahahah yes I do believe that's what I mean haha

Sent from my iPhone

On 1 Jun 2015, at 3:22 pm, Tom Bentley notifications@github.com wrote:

that's 1 i.e. the additive identity.

I think you mean the multiplicative identity, the additive identity is zero.

— Reply to this email directly or view it on GitHub.

CeylonMigrationBot commented 9 years ago

[@FroMage] The problem is that Java's enums allow brevity, multiple constructors and anonymous subtyping. Brevity (even with multiple constructors) could be greatly improved using this syntax (provided we can get at the case values from outside, which is not obvious):

class FamousNumbers(shared Integer number)
 of beast(666) | answer(42) {}

class UsesConstructors
 of first(1) | second(34, 12) {
 shared Integer prop;
 new Foo(Integer x){
  prop = x;
 }
 new Bar(Integer x, Integer y){
  prop = x * y;
 }
}

As for anonymous subtyping (which I frequently do in enums), we could do it like this too:

class FamousNumbers(shared Integer number)
 of answer(42) 
  | beast(666) { bow() => throw Exception("I bow to no one"); }  
{
 shared default void bow(){
  print("I am number ``number``);
 }
}

It isn't pretty, but it works, and is even less verbose than Java's equivalent:

public enum FamousNumbers {
 Answer(42),
 Beast(666) { 
  @Override
  public void bow() {
   throw new Exception("I bow to no one");
  }
 };
public final int number;
FamousNumbers(int number) { this.number = number; }
public void bow(){
  System.err.println("I am number "+number);
 }
}
CeylonMigrationBot commented 9 years ago

[@FroMage] And if we're worried that the case values would default to shared (because that's the most useful thing to have) when it's not explicit (and so we can't turn it off) we could make enum be an annotation for the class that just means "make case values be shared by default", so we can turn it off by leaving the enum annotation out.

CeylonMigrationBot commented 9 years ago

[@gavinking] So I think a reasonable solution might be to let you abbreviate the common case where the values don't have any interesting initialisation logic:

class Bool of true | false {}

This isn't ambiguous at all, it can't be confused with anything we have today.

Then, if one of your enumerated cases needs dedicated init logic, you add a singleton constructor with that name.

CeylonMigrationBot commented 9 years ago

[@FroMage] Can you give me the current proposal's equivalent to the examples I posted (taken straight off the compiler code, though simplified. they are not hand-waving but real use-cases)? It's a bit hard to judge the current proposal without samples…

CeylonMigrationBot commented 9 years ago

[@gavinking] Wait, wat, this is just a dog's breakfast:

class UsesConstructors
    of first(1) | second(34, 12) {
  shared Integer prop;
  new Foo(Integer x){
    prop = x;
  }
  new Bar(Integer x, Integer y){
    prop = x * y;
  }
}

So first(1), what is that—looks like a function call, where is the function named first(). Hrm, can't find it. Oooh OK, it's a constructor invocation ... but, which constructor, I have Foo and Bar here, oh, wait, you're matching argument lists to constructors based on the arity of the parameter list?! What happens if that's ambiguous?

Nononononono. What I am proposing lets me write:

class UsesConstructors
      of first | second {
  shared Integer prop;
  shared new first {
    prop = 1;
  }
  shared new second {
    prop = 34*12;
  }
}

That's clearly much cleaner.

Now the second example, given as:

class FamousNumbers
      (shared Integer number)
    of answer(42) 
      | beast(666) { 
        bow() => throw Exception("I bow to no one"); 
      }  
{
  shared default void bow() {
     print("I am number");
  }
}

(Note I had to reformat this over a bunch more lines to even begin to make sense of this.)

Well there's several problems with this:

  1. The syntax is ambiguous, we don't know whether { is the start of the class or the start of a case.
  2. It seems to break definite initialization checking since it refers to bow() before bow() is defined.

OTOH, with the current proposal, it could look like something like this:

class FamousNumbers
    of answer | beast
{
  shared Integer number;
  shared void bow();
  shared new answer { 
    number=42; 
    bow() => print("I am number");
  }
  shared new beast {
    number = 666;
    bow = () { throw Exception("I bow to no one"); }
  }
}

There's absolutely no debate in my mind about which of these is cleaner, more readable, more easy to format, and keeps implementation and API more separate.

CeylonMigrationBot commented 9 years ago

[@gavinking] By the way, @FroMage one comparison you can just take home right now is the comparison to Java enums, which is totally inapt, since they are not exhaustive. The following does not compile:

String toString(SiteVariance var) {
    switch (var) {
    case IN: return "out" 
    case OUT: return "out";
    case NONE: return "";
    }
}

In Java I am forced to write:

String toString(SiteVariance var) {
    switch (var) {
    case IN: return "out" 
    case OUT: return "out";
    case NONE: return "";
    default: throw Error("the universe is about to end!");
    }
}

So to compare my proposal, fairly to Java, you have a class with no of clause and just:

class SiteVariance {
    shared new in {}
    shared new out {}
    shared new none {}
}
CeylonMigrationBot commented 9 years ago

[@FroMage] Except you're conveniently skipping over the issue of constructor reuse. What if I have more values that need the same constructors?

CeylonMigrationBot commented 9 years ago

[@lucaswerkmeister](for future reference, and the benefit of others interested in this issue: the discussion has for some reason moved – temporarily? permanently? who knows – [to Gitter]%28https://gitter.im/ceylon/user?at=556c6eca463d0c7c066d9820%29)

CeylonMigrationBot commented 9 years ago

[@gavinking]

Except you're conveniently skipping over the issue of constructor reuse. What if I have more values that need the same constructors?

Then write a partial constructor and delegate to it. That was part of the original proposal that you apparently didn't bother to read.

CeylonMigrationBot commented 9 years ago

[@gavinking] So by thinking through the usecases for Java-style enums I've concluded that the distinction between "open-ended" enums, which all Java enums are, implicitly, and "closed-ended" enums, which let you exhaustively switch over, is a real one. Consider a list of error types, for example. That's almost certainly an open-ended enum. But people want to be able to write Error.missingClosingParen or whatever.

I'm honestly not sure about the case of Directionality in ceylon.unicode, is that exhaustive or not? I'm a bit suspicious that any enum with that many cases can possibly be exhaustive—especially when one of its cases is the rather suspicious looking "undefined"—but given that this is defined by a spec that changes slowly, I guess we could treat it that way.

So, assuming that we do need it to be closed-ended, what would this look like using singleton constructors?

"Enumerates the *Directionalities* defined by the Unicode 
 specification."
shared class Directionality
        of arabicNumber 
        | boundaryNeutral
        | commonNumberSeparator
        | europeanNumber
        | europeanNumberSeparator
        | europeanNumberTerminator
        | leftToRight
        | leftToRightEmbedding
        | leftToRightOverride
        | nonspacingMark
        | otherNeutrals
        | paragraphSeparator
        | popDirectionalFormat
        | rightToLeft
        | rightToLeftArabic
        | rightToLeftEmbedding
        | rightToLeftOverride
        | segmentSeparator
        | undefined
        | whitespace {

    "The two character code assigned to this directionality 
     by the Unicode specification."
    shared String code;

    shared new arabicNumber { code=>"AN"; }
    shared new boundaryNeutral { code=>"BN"; }
    shared new commonNumberSeparator { code=>"CS"; }
    shared new europeanNumber { code=>"EN"; }
    shared new europeanNumberSeparator { code=>"ES"; }
    shared new europeanNumberTerminator { code=>"ET"; }
    shared new leftToRight { code=>"L"; }
    shared new leftToRightEmbedding { code=>"LRE"; }
    shared new leftToRightOverride { code=>"LRO"; }
    shared new nonspacingMark { code=>"NSM"; }
    shared new otherNeutrals { code=>"ON"; }
    shared new paragraphSeparator { code=>"B"; }
    shared new popDirectionalFormat { code=>"PDF"; }
    shared new rightToLeft { code=>"R"; }
    shared new rightToLeftArabic { code=>"AL"; }
    shared new rightToLeftEmbedding { code=>"RLE"; }
    shared new rightToLeftOverride { code=>"RLO"; }
    shared new segmentSeparator { code=>"S"; }
    shared new undefined { code=>""; }
    shared new whitespace { code=>"WS"; }

}

Is that perfect? Perhaps not. Is it better than what we have today? Well, yes, I think it's clear that it's somewhat cleaner.

OTOH, if we don't think we need to make this closed-ended, which is the fair comparison to Java enums, we would have just this:

"Enumerates the *Directionalities* defined by the Unicode 
 specification."
shared class Directionality {

    "The two character code assigned to this directionality 
     by the Unicode specification."
    shared String code;

    shared new arabicNumber { code=>"AN"; }
    shared new boundaryNeutral { code=>"BN"; }
    shared new commonNumberSeparator { code=>"CS"; }
    shared new europeanNumber { code=>"EN"; }
    shared new europeanNumberSeparator { code=>"ES"; }
    shared new europeanNumberTerminator { code=>"ET"; }
    shared new leftToRight { code=>"L"; }
    shared new leftToRightEmbedding { code=>"LRE"; }
    shared new leftToRightOverride { code=>"LRO"; }
    shared new nonspacingMark { code=>"NSM"; }
    shared new otherNeutrals { code=>"ON"; }
    shared new paragraphSeparator { code=>"B"; }
    shared new popDirectionalFormat { code=>"PDF"; }
    shared new rightToLeft { code=>"R"; }
    shared new rightToLeftArabic { code=>"AL"; }
    shared new rightToLeftEmbedding { code=>"RLE"; }
    shared new rightToLeftOverride { code=>"RLO"; }
    shared new segmentSeparator { code=>"S"; }
    shared new undefined { code=>""; }
    shared new whitespace { code=>"WS"; }

}

Ultimately I don't think any of this is especially offensive on the eyes.

CeylonMigrationBot commented 9 years ago

[@gavinking] Well one solution to this quandary would be to use a different keyword to declare the singleton constructors when they represent enumerated cases. We would keep using new when it's an "open-ended" enumeration, and use of when it's "closed":

"Enumerates the *Directionalities* defined by the Unicode 
 specification."
shared class Directionality {

    "The two character code assigned to this directionality 
     by the Unicode specification."
    shared String code;

    of arabicNumber { code=>"AN"; }
    of boundaryNeutral { code=>"BN"; }
    of commonNumberSeparator { code=>"CS"; }
    of europeanNumber { code=>"EN"; }
    of europeanNumberSeparator { code=>"ES"; }
    of europeanNumberTerminator { code=>"ET"; }
    of leftToRight { code=>"L"; }
    of leftToRightEmbedding { code=>"LRE"; }
    of leftToRightOverride { code=>"LRO"; }
    of nonspacingMark { code=>"NSM"; }
    of otherNeutrals { code=>"ON"; }
    of paragraphSeparator { code=>"B"; }
    of popDirectionalFormat { code=>"PDF"; }
    of rightToLeft { code=>"R"; }
    of rightToLeftArabic { code=>"AL"; }
    of rightToLeftEmbedding { code=>"RLE"; }
    of rightToLeftOverride { code=>"RLO"; }
    of segmentSeparator { code=>"S"; }
    of undefined { code=>""; }
    of whitespace { code=>"WS"; }

}

That's not bad at all.

CeylonMigrationBot commented 9 years ago

[@gavinking] So perhaps what we could say is that:

class Bool {
    of false {}
    of true {}
}

Would be an abbreviation for:

class Bool 
       of false | true
       satisfies Enumerated {
    shared actual String string;
    shared actual Integer integer;
    shared new false { string=>"false"; integer=0; }
    shared new true { string=>"true"; integer=1; }
}

Thus member ofs would just be a syntax sugar to cut down on the boilerplate.

None of that would conflict with what is proposed in #4231. We could still support this in future:

class Point {
    ...
    of origin { ... }
    of Polar(Float x, Float y) { ... }
    of Cartesian(Float r, Float theta) { ... }
}
CeylonMigrationBot commented 9 years ago

[@gavinking] Hell, if this is just an abbreviation, I suppose we could even make the enumerated annotation have that effect.

enumerated class Bool {
    new false {}
    new true {}
}
CeylonMigrationBot commented 9 years ago

[@jvasileff] Can the case types be made denotable to do things like work with type safe subsets?

CeylonMigrationBot commented 9 years ago

[@gavinking] @jvasileff perhaps in conjunction with #4231.

CeylonMigrationBot commented 9 years ago

[@gavinking] So the more I think about it, the more I'm inclined to think that an enumerated annotation does almost no work. It is quite analogous to final, in fact, much more than I thought at first, in the sense that having that annotation there lets the client do some stuff that they would not otherwise be allowed to. (In the case of final, types become disjoint, in the case of enumerated, types become exhaustive.)

But there is a big difference which shows why enumerated is pretty lame. If I "break" finality, and attempt to add a new subclass, the compiler forces me to remove the final annotation, thus unambiguously telling me that I've made a significant breaking change. But if I "break" clients of an enumerated class, by adding a new constructor, the compiler does not complain until I try to recompile clients.

An of clause list is quite different: if I add a new subtype of the enumerated class, I have to go back to the enumerated class and explicitly add that subtype to the list.

So I'm kinda left torn between two options:

  1. Just treat every switch as exhaustive if it covers all constructors. If you want to prevent clients from switching on your class, because you think you might add new constructors, add a private constructor. In practice there's really no way to be sure that an enumerated type won't add new cases in the future. The future is unknowable.
  2. Require the explicit of list, even though it's really unDRY.

I lean toward option 1. The original design before I had a crisis of confidence yesterday.

CeylonMigrationBot commented 9 years ago

[@gavinking] P.S. Who else here prefers of instead of new for defining singleton constructors?

CeylonMigrationBot commented 9 years ago

[@tombentley]

P.S. Who else here prefers of instead of new for defining singleton constructors?

I did quite like that, actually.

CeylonMigrationBot commented 9 years ago

[@gavinking] No, wait, the above reasoning just can't be correct. People add new constructors for convenience to classes all the time, without wanting to bother themselves with the possibility that this is a change that breaks clients. We make constructors far less useful if we make it problematic to add new constructors.

P.S. There's also an issue with delegation here, but I think that's solvable by saying that:

  1. constructor aliases #4236 aren't reified, so don't need to be treated as cases,
  2. partial constructors #4318 also aren't reified, so aren't treated as case, and
  3. all other constructors are reified as individual cases.

So I'm pretty much back to where I was yesterday:

  1. an explicit of list for enumerated classes,
  2. use of instead of new when writing the constructors of an enumerated class, or
  3. kick the can down the road on the issue of exhaustive switches and just require you to write an else clause when switching on constructors.
CeylonMigrationBot commented 9 years ago

[@quintesse] I'm still in favor of #3107 , I like it being explicit, I like that you'll be forced to think about it a bit more when you want an exhaustive switch and I don't mind at all that you have to repeat the name, not even when there's 2 dozen of them.