Regarding sealed types, I'm not really sure why you'd choose these over type unions, in practice they're often used to describe things like events for reducers and using sealed is incredibly verbose compared to the equivalent in for instance typescript.
type Message =
| { type: 'IncrementBy', value: number }
| { type: 'DecrementBy', value: number }
| { type: 'Set', value: number };
vs sealed class Message {}
class IncrementBy extends Message {
final int value;
IncrementBy(this.value);
}
class DecrementBy extends Message {
final int value;
DecrementBy(this.value);
}
class Set extends Message {
final int value;
Set(this.value);
}
In a real app this noise really adds up making it difficult to get an overview of the types.Record types are nearly there, but currently there isn't a way to update the records which really hobbles them in day to day use (it looks there will be record spreading coming though so this is hopefully temporary).
With primary constructors this will become something along the lines of:
sealed class Message();
class IncrementBy(final int value) extends Message;
class DecrementBy(final int value) extends Message;
class Set(final int value) extends Message;
Which is considerably less repetitive, though `extends Message` is still there. I am fairly optimistic that the next step would be to eliminate that[1], though I think we would need to gather a bit more feedback from users as people are getting more and more reps in with Dart 3.0 features. I personally would prefer something along the lines of: sealed class Message() {
case IncrementBy(final int value);
case DecrementBy(final int value);
case Set(final int value);
}
Current syntax is not all that bad if you are going to do OO and add various helper methods on `Message` and its subclasses, but if you just want to define your data and no behavior / helpers - then it is exceedingly verbose.I guess ultimately it depends on which language you are more familiar with...
type Message
= IncrementBy Int
| DecrementBy Int
| Set IntIn Dart, if you do:
sealed class Message {}
class IncrementBy extends Message {
final int amount;
IncrementBy(this.amount);
}
class DecrementBy extends Message {
final int amount;
DecrementBy(this.amount);
}
class Set extends Message {
final int amount;
Set(this.amount);
}
You can then write functions that accept specific cases, like: onlyOnIncrement(IncrementBy increment) {
...
}
In Elm and friends, IncrementBy is just a type constructor, not a type. Further, we can use the class hierarchy to reuse shared state and behavior in a way that sum types don't let you easily do. In your example, each case has an int and it so happens that they reasonably represent roughly the same thing, so you could do: sealed class Message {
final int amount;
Message(this.amount);
}
class IncrementBy extends Message {
IncrementBy(super.amount);
}
class DecrementBy extends Message {
DecrementBy(super.amount);
}
class Set extends Message {
Set(super.amount);
}
And now you can write code that works with the amount of any Message without having to pattern match on all of the cases to extract it: showAmount(Message message) {
print(message.amount);
}
So, yes, it's more verbose than a sum type (and we do have ideas to trim it down some), but you also get a lot more flexibility in return.It's only more verbose for the code that is defining new types. Code that is simply defining behavior (either in functions or methods) is unaffected and my experience is that that's the majority of code.
In practice, most real-world Dart code that uses sealed types also defines methods in those types, getters, and all sorts of useful stuff. Once you factor that in, the data definitions themselves are a relatively small part.
(Of course, you could argue that defining class hierarchies is already intrinscally bad. But Dart is an object-oriented language targeting people that like organizing their code using classes.)
type Message
= IncrementBy Amount
| DecrementBy Amount
| Set Amount
type Amount = Amount Int
Obviously this is a contrived example - you wouldn't bother if you were dealing with an Int but once the message gets more complicated it can make sense.Given the above ADT, how would you write a function that prints the amount of a Message, regardless of which case it is?
Of course, you can also reorganize you datatype so that the shared state is hoisted out into a record that also contains a sum typed field for the non-shared stuff. But that reorganization is a breaking change to any consumers of the type.
Modeling this on top of classes and subtyping lets you make those changes without touching the user-visible API.
And this Flutter binding launched a year ago by one person and then never touched again: https://github.com/fable-compiler/Fable.Flutter
It just seems like an incredibly ambitious project that appears to have very little equal but is mainly worked on by a handful of people but no corporate backing. I get the feeling that if you want to use it, you'll either be the only one doing what you're doing or among just a few people. I already use F# and feel this way about the core language itself.
Agreed.. I've definitely Googled an issue I got stuck on only to land back on my own post (lol). You need fewer people for the same amount of complexity with F#, but idk if that's somehow just translating to fewer people period?