Haha. That's alright. That looks super neat though, I must admit. The only equivalent that comes to mind would be using an abstract class. I still might fail to fully understand what that code example does, but, would this be somewhat similar?
abstract class AppState {}
class AppLoading extends AppState {
final int progress;
}
class AppSelectingLevel extends AppState {}
class AppPlaying extends AppState {
final Level level;
}
...
Certainly not as neat, but also, not that far off either. Then where you use this app state, you could switch over its `runtimeType`, and in each clause, the IDE will understand which implementation you are dealing with, and actually give you context sensitive help related to that particular state.
If you instead only cared about one case, you would be able to do:
if (appState is AppPlaying) {
doSomething(appState.level);
}
Would this be somewhat analogous to the functionality you get with unions in typescript?
The TS code indeed looks cool. This is an area we're looking at.
One point, though: we try to be very careful to not regress performance or developer iteration time (e.g., type checking time) when we introduce new language features. E.g., structural typing can be more expensive in general to type check since we need to recurse.
Have you considered not going full-on structural-typing but still providing some sort of union? In fact, you could go for one with even stronger guarantees, like the sum types in Rust or F#. (with Rust going as far as to call them enums too)
I'll admit I have the faintest notion on what causes that kind of complexity on a compiler, so my suggestion might be an even worse idea.
> Have you considered not going full-on structural-typing but still providing some sort of union?
I work on Dart. The terminology gets really confusing here. "Discriminated unions" and "union types" are two quite different things, though they get a little blurry in TS.
The short answer is, yes, we're investigating pattern matching with exhaustiveness checking and making the language more graceful at expressing algebraic datatype-style code. The last half of that sentence sounds weasely because any object-oriented language can (except for exhaustiveness checking) model an algebraic datatype using subtyping. The parent comment using an abstract base class is how you do it.
So there isn't anything really fundamental that Dart can't express already. It's mostly a question of giving users syntactic sugar to make code in that style look more terse and familiar. I personally really like pattern matching and ADTs and I also particularly like multi-paradigm languages, so this is a subject close to my heart.
The language team has been pretty busy with null safety, but now that that's out the door (woo!), we can start working on the next batch of features, which with luck includes pattern matching. Here's an in-progress proposal:
No time frame, sorry. We generally don't make promises about future dates because schedules tend to be flexible and picking dates just sets people up for disappointment.
Indeed, that is basically what you get from unions, except the exhaustiveness check.
Unless I'm mistaken, if one were to later implement a new class that extends AppState, all existing code would compile, but possibly fail or misbehave at runtime, unless you meticulously checked every place that tries to determine something based on those derived types.
In TypeScript, adding a new case for an union and not handling it everywhere is a compilation error on every incomplete usage site.
I have to say, the default diagnostic isn't brilliant, but some tooling will give a better error and actually point out the missing arms, instead of complaining about the return type.
I suppose. In practice, I haven't experienced this to be a problem. Since you already check which implementation you are dealing with, any code that relied on any state, should still work without any issue. This is the same as with typescript unions. Any code that somehow needs to handle a new state hm... I suppose getting a compile time error is nice to immediately see all places where it is used... but, I mean, so would a "find all uses" search. It's also not all that different from the linting warning error you'd get from iterating over runtime types without handling all cases.
All in all, sufficiently analogous to not consider unions a missing feature of the dart language? Seems nice to have, but, maybe not very necessary. Especially if the only difference is whether it is considered an error by the syntax, or a warning by the linter.
The proper analog to union types in Dart (as in Java) is enums and / or church-encoding [I think that's the term] generalized algebraic data types (GADTs). E. g. something like this: https://gist.github.com/jbgi/208a1733f15cdcf78eb5
Scala 2 also had `sealed` classes that could be used in places where you needed enums parameterized by runtime values and that's been generalized in Scala 3 IIRC.
I'm certainly confused now whether or not we are talking about the same thing. Without delving to much on the use of the word "union", how would you solve the use case presented in the typescript examples using enums?
If you instead only cared about one case, you would be able to do:
Would this be somewhat analogous to the functionality you get with unions in typescript?