Comments (62)
I also was initially put off by the lack of ADTs in Dart. But while there isn't language-level support, ADTs can be decently represented in Dart. For example, here's how I would translate your Kotlin example into Dart:
abstract class LoginResponse {
// Not necessary when case classes are public,
// but I usually put factories and constants on the base class.
factory LoginResponse.success(authToken) = LoginSuccess;
static const invalidCredentials = const InvalidLoginCredentials();
static const noNetwork = const NoNetworkForLogin();
factory LoginResponse.unexpectedException(Exception exception) = UnexpectedLoginException;
}
class LoginSuccess implements LoginResponse {
final authToken;
const LoginSuccess(this.authToken);
}
class InvalidLoginCredentials implements LoginResponse {
const InvalidLoginCredentials();
}
class NoNetworkForLogin implements LoginResponse {
const NoNetworkForLogin();
}
class UnexpectedLoginException implements LoginResponse {
final Exception exception;
const UnexpectedLoginException(this.exception);
}
And for the when
statement:
void on(LoginResponse loginResponse) {
if (loginResponse is LoginSuccess) {
// Dart has smart casting too, so you can use authToken w/o a cast
loginResponse.authToken;
} else if (loginResponse is InvalidLoginCredentials) {
} else if (loginResponse is NoNetworkForLogin) {
} else if (loginResponse is UnexpectedLoginException) {
// Dart has smart casting too, so you can use exception w/o a cast
loginResponse.exception;
} else {
// Dart doesn't have sealed classes
throw new ArgumentError.value(loginResponse, 'loginResponse', 'Unexpected subtype of LoginResponse');
}
}
Does this satisfy your request for ADTs? I've been using this pattern myself for a while, and at this point I don't think that Dart needs language-level support for ADTs per se (though I wouldn't complain if it was added).
Because Dart does not have sealed classes or a when
-like statement, you can have runtime type errors, which is the one thing I'm really not happy with. I'd be in favor of adding those to Dart. However, as has been pointed out elsewhere, those are really a separate, broader issue than ADTs.
from language.
Hey @avilladsen --
The features I would like (whether it's sealed classes or union types):
- Exhaustive Checking with automatic casting inside
when
+is
expressions. This provides static analysis to ensure you're handling each and every case. This eliminates the need for some types of tests. - Constrained types. Not open for extension outside of the declaration. This makes reading the code a breeze, since you can see "Ok, this type encapsulates exactly these responsibilities".
- Concise syntax without
==
,toString
, andhashcode
andcopy
boilerplate, although I think "data classes" are a separate issue. For example, none of your classes include these methods, and they need to be implemented for testing to work more easily. - Convenient Domain Modeling: https://skillsmatter.com/skillscasts/4971-domain-driven-design-with-scott-wlaschin
Sure, defining a class with some subclasses will work in combination with is
checks, but it's not as safe (lack of exhaustive checking, need to write more tests) and more verbose. I think you can see a great example of modeling state in this fashion here: https://github.com/freeletics/RxRedux#usage
You can see an attempt that some of us in the community have worked on to achieve this: https://github.com/fluttercommunity/dart_sealed_unions
You could also use built_value
to generate the ==
, toString
etc, but that is also difficult to read and requires a build step.
Therefore, we can paper over these issues with libraries, but they're far more cumbersome and much harder to read than workin with sealed + data classes in Kotlin. In fact, I even prefer the Scala version with pattern matching and destructuring: https://docs.scala-lang.org/tour/pattern-matching.html
from language.
ADTs can be decently represented in Dart
I'm looking for language level support, unfortunately. A modern programming language should make it easy to represent ADTs.
At least Dart has smart casting
from language.
I've been developing in Flutter for two weeks and I've been really blown away by the incredibly well thought of framework and surrounding tooling!
I think Flutter is the biggest progress when it comes to developer productivity for UI programming since Visual Basic 1.0 / Delphi 1.0 et al in the mid nineties.
But I really miss the beautiful sum types of the ML-family (OCAML, F#, ReasonML, Haskell etc).
Please compare the Dart example from @avilladsen :
abstract class LoginResponse {
factory LoginResponse.success(authToken) = LoginSuccess;
static const invalidCredentials = const InvalidLoginCredentials();
static const noNetwork = const NoNetworkForLogin();
factory LoginResponse.unexpectedException(Exception exception) = UnexpectedLoginException;
}
class LoginSuccess implements LoginResponse {
final authToken;
const LoginSuccess(this.authToken);
}
class InvalidLoginCredentials implements LoginResponse {
const InvalidLoginCredentials();
}
class NoNetworkForLogin implements LoginResponse {
const NoNetworkForLogin();
}
class UnexpectedLoginException implements LoginResponse {
final Exception exception;
const UnexpectedLoginException(this.exception);
}
to how you would write the same in say F#:
type LoginResponse =
| Success of AuthToken
| InvalidCredentials
| NoNetwork
| UnexpectedException of Exception
(I use the same shorter names as in @ZakTaccardi's original Kotlin example)
You get much better data/domain modelling inside your code when you have tools like this available in the language (@brianegan makes the same argument). It improves readability and quality of the code, not the least because it enables preventing many more illegal states at compile time. And since it is so easy to read and write, you do it right instead of often cheating like you would do in say Dart because you would have to write so much more code to do it "correctly".
Dart is a nice language, and Flutter is a killer. For me, the biggest lack in Flutter is these data modelling capabilities (and pattern matching) of languages of the ML family. (Scala and Kotlin have similar capabilities, but more verbose syntax)
For non-trivial UI I quite often find finite state automata very useful and sum types (and the corresponding pattern matching) is perfect for this.
from language.
I'm really really missing this coming from Swift/iOS to Flutter development.
When dealing with widget/component state, I find it that almost always the state is better described using an ADT rather than several nullable fields. The problem with nullable fields is that it often allows for combinations of values that are simply invalid.
Here is an example from a commercial app I'm currently porting from Swift to Flutter:
class _State {}
class _ChooseProviderState extends _State {}
class _LoadingState extends _State {}
class _ChooseAccountsState extends _State {
final List<String> accounts;
final Set<String> selected;
_ChooseAccountsState(this.accounts) : selected = Set();
}
class _ImportingState extends _State {
final List<String> imported;
final List<String> failed;
final List<String> processing;
final List<String> queue;
_ImportingState(this.imported, this.failed, this.processing, this.queue);
}
// ...
@override
Widget build(BuildContext context) {
if (_state is _ChooseProviderState) {
return _buildChooseProvider(context, _state);
}
if (_state is _LoadingState) {
return _buildLoading(context, _state);
}
if (_state is _ChooseAccountsState) {
return _buildChooseAccounts(context, _state);
}
if (_state is _ImportingState) {
return _buildImporting(context, _state);
}
throw Exception('Invalid state');
}
Apart from being very verbose and cumbersome to type out, there is also no compiler checks that I'm 1) covering all possible states and that 2) there isn't every any other state. This forces me to add a runtime throw
in my code.
Now let's compare this to the Swift equivalent:
enum State {
case chooseProvider,
case loading,
case chooseAccounts(accounts: [String], selected: Set<String>),
case importing(imported: [String], failed: [String], processing: [String], queue: [String]),
}
// ...
build(context: BuildContext) -> Widget {
switch state {
case .chooseProvider:
return buildChooseProvider(context)
case .loading:
return buildLoading(context)
case .chooseAccounts(let accounts, let selected):
return buildChooseAccounts(context, accounts, selected)
case .importing(let imported, let failed, let processing, let queue):
return buildImporting(context, imported, failed, processing, queue)
}
}
Apart from being much much easier to work with, and quickly glance over, it gives me additional compiler guarantees. Here I know that I'm never missing any of the enum cases since the compiler will complain. I also don't have to add a runtime throw
, since the compiler can guarantee that execution will never continue after the switch
statement.
I would absolutely love to see this in Dart 😍 is there anything I can do to move this forward?
from language.
Concise syntax without ==, toString, and hashcode and copy boilerplate, although I think "data classes" are a separate issue.
This is the main source of boilerplate code for me. And this is a lot of boilerplate.
Not sure if ADTs are supposed to address this, but It’s probably number 1 feature in my wishlist.
from language.
Closing this because records and patterns are now enabled on the main branch of the Dart SDK! 🎉
from language.
Apologies if this is a bit of a +1 response, but coming from Rust I really miss ADTs (called enum
s in rust). Rust's optional Option
is vastly superior to everything being nullable - you don't need to annotate @required
, the type tells you if it is or not! Rust's Result
, which is like Either
in Haskell is also a great way to represent fallible operations - quite often the error type is also an ADT with extra data depending on the type of the error. They are also really easy to implement and memory efficient - they are just typed unions from C (although I don't know dart's memory model, maybe everything is behind a pointer anyway).
EDIT To full realise their power you also need some de-structuring support (sometimes called switch
/match
/case
). In rust say you have an ADT like
enum Error { // ADT
IoError { // 1st variant with named fields
errno: u32 // unsigned 32-bit int
}, // 2nd variant with named fields
ParseError {
position: usize // unsigned pointer-width int
msg: String
},
UnknownError, // 3rd variant with no fields
}
then the destructuring would look like
match e { // e is an error
Error::IoError { errno } => {
// I have access to the fields of the variant here
// handle the error...
}
Error::ParseError { position, msg } => {
// again I have access to fields
}
Error::UnknownError => {
// nothing to bind here
}
}
from language.
The Dart team is focused in NNBD now. All we can now is to thumbs up this issue and keep an eye in here.
Yes, and we do very much appreciate your patience. :) We are also very much looking forward to finishing up NNBD and moving onto other language features.
from language.
Remi Rousselet, who made the great provider, functional_widget, and flutter_hooks libraries, among others, recently launched the freezed package that includes immutable classes and also algebraic data types, along with an exhaustive when
function to switch between the unions.
You can create immutable classes using an abstract class with the @immutable
annotation and the specified properties:
@immutable
abstract class Person with _$Person {
factory Person(String name, int age) = _Person;
}
For ADTs, create an abstract class with factories specifying their values, and use when
to pattern match them:
@immutable
abstract class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
}
var model = Model.first('42');
print(
model.when(
first: (String a) => 'first $a',
second: (int b, bool c) => 'second $b $c'
),
); // first 42
There's also maybeWhen
, similar to other languages where there is a default case, using the orElse
keyword:
@immutable
abstract class Union with _$Union {
const factory Union(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String message]) = ErrorDetails;
}
var union = Union(42);
print(
union.maybeWhen(
null, // ignore the default case
loading: () => 'loading',
// did not specify an `error` callback
orElse: () => 'fallback',
),
); // fallback
It looks to be a pretty cool library. I'm glad that Dart has code generation so that we can create language features via libraries instead of just waiting for them by the official Dart team, although language features would be preferred over libraries of course, if only to cut down the amount of generated code and boilerplate.
from language.
I'm not sure if commenting here will be helpful all, but I'm a big +1 to this feature request. When it comes to modeling a problem domain in a modern programming language, I'd say ADTs are very nearly as important as null safety. With null-safety implemented, I can't imagine any language feature that ought to be a higher priority than ADTs. It's similar to null-safety in the sense that you can't properly model your problem domain without it.
Flutter being such an important use of Dart implicitly puts Dart in direct competition with Swift and Kotlin. Both of those languages not only support ADTs, but have a strong developer culture of using them heavily. That means that while there is a lot about the developer experience that improves when one switches from native to Flutter, the lack of ADTs in Dart is a glaring regression that is very noticeable to iOS and Android devs.
from language.
All of these external packages feel like bandaid solutions to something that would likely be able to optimize heavily if it were in the language as a standard feature.
from language.
Chiming in to say that having spent the last few years neck deep in Rust and Swift, coming to Dart without ADT feels a little like having a hand tied behind your back.
This really is a must have language feature and would enable easier creation of robust code.
from language.
How long can I wait? To write secure code, you have to suffer. Sealed classes have been implemented in all modern languages for a long time.
from language.
I want data classes and removing the
;
.
These are tracked on separate issues.
And also enum class bring nothing but string and index!
The next stable release of Dart will allow you to define fields, methods, and other members on enum types. It's out in the beta channel now.
from language.
Another vote for this feature
from language.
Just adding OCaml's variant types to the conversation here.
A sum type in OCaml looks like:
type FeedWidgetState =
| Loading
| Failed of exn * stack_trace
| Succeeded of tweet list
let build state context =
match state with
| Loading -> Flutter.circularProgressIndicator
| Failed e _ ->
let errMsg = Printexc.to_string e in
Flutter.center (Flutter.text errMsg)
| Succeeded accounts ->
let render_account account =
Flutter.listTile ~title: (Flutter.text account.handle)
in
Flutter.listView ~children: (List.map render_account accounts)
Obviously, the syntax is pretty different from Dart, but theoretically a translation could still look pretty Dart-y:
sum class FeedWidgetState {
Loading,
Failed(Object error),
Succeeded(List<Account> accounts)
}
Widget build(BuildContext context) {
// Or: match (await someComputation(2, "3")) as state
return match state {
Loading => CircularProgressIndicator(),
Failed => Center(child: Text(state.error.toString())),
Succeeded {
return ListView(
children: state.accounts.map(renderAccount).toList(),
);
}
}
}
EDIT: It would also be cool to potentially even allow method/field definitions in sum class, though that might be a little too much to ask for...
sum class FeedWidgetState {
Loading,
Failed(Object error),
Succeeded(List<Account> accounts)
String common() => "hello!"
void doIt() {
// Do it...
}
@override
String toString() {
return match this {
// ...
}
}
}
// OR...
class FeedWidgetState {
sum Foo(), Bar(String x), Baz(List<String> quux)
from language.
Seems to me that the effort creating a new library might have been better spent improving Sealed Unions as it is already being widely used.
Still at least there is choice :)
from language.
I like Flutter, but Dart language in general, especially Enum in Dart, is holding me back :(. Hope that, Dart will soon be comparable with modern languages like Kotlin, Swift or C#.
from language.
ADTs are also known as "tagged unions".
In typescript, you may represent the above example as:
type Success = {
tag: "Success"
authToken: string;
};
type InvalidCredentials = {
tag: "InvalidCredentials"
};
type NoNetwork = {
tag: "NoNetwork"
};
type UnexpectedException = {
tag: "UnexpectedException"
error: Error
};
type LoginResponse =
Success
| InvalidCredentials
| NoNetwork
| UnexpectedException
The tag
property is shared by all the different structural types, which allows us to use a switch statement to identify which type we have.
Option<T>
from Rust, which is referenced above, is another example of a tagged union.
There is also the concept of an "untagged union", which could simply refer to having one object with nullable fields represent multiple scenarios:
type Option<T> = {
isPresent: bool;
value?: T;
};
Untagged unions can also refer to syntax sugar for dealing with nullable types:
_optionalVar?.foo();
Most languages with nullable types can support untagged unions, but Dart clearly has a special understanding since it supports the safe-access operators.
In addition, dart (and similar style languages) provide smart casting (and "instance of" checking) on exceptions with try/catch statements.
Pure OO practices would discourage the use of ADTs, since it involves "casting" an abstract class to a concrete type after doing an "instance of" check. The OO pattern that would replace the need for ADTs is the visitor pattern, but this adds so much overhead just to stay in the OO space that the benefit is often not worth the cost.
Also these languages usually provide exception type checking, which violates that pattern anyways.
I'm not super familiar with how the dart compiler works, but if we are able to identify all the implementation of an abstract class at compile time, it might be possible to remove the need for the else
statement at the end a series of is
checks.
from language.
That may be the case but I'm sure a language level solution would be much better than ad-hoc with third-party libraries, and would also expose opportunities for compiler level optimizations. Not to mention that the documentation might be a lot more user friendly rather than a "go read this thing written for another language".
from language.
hiya, any updates on ADT support? 🙂
The Dart team is focused in NNBD now. All we can now is to thumbs up this issue and keep an eye in here.
from language.
I think the overall experience writing dart is good but I do miss some things and by reading here it looks like I'm not alone. It would be great if we could have the equivalent of:
- Sealed interfaces.
- Type matching in switch (or some other keyword like
when
) which could be exhaustive for sealed interfaces. - Data classes or records to represent data with automated copyWith, equals and hashCode. I think the main use case is for immutable data, but maybe it doesn't need to be restricted to it.
Packages like freezed
are great but having to rely on code generation for this common cases feels sub optimal in my opinion.
The good news is that it looks like there are issues for most of these and are in various levels of discussions already. It's great we are getting fields in enums in the next release!
from language.
Can you be specific about what you're looking for in language-level support that is not currently possible in Dart? Is it the more concise syntax for declaring ADTs? The static checking of when
-style statements?
from language.
I did something similar to what @avilladsen and @renatoathaydes mentioned above and was satisfied with the results. To avoid the boilerplate, I built a code generator with my colleague which generates these classes by annotating Enums.
@superEnum
enum _MoviesResponse {
@Data(fields: [DataField('movies', Movies)])
Success,
@object
Unauthorized,
@object
NoNetwork,
@Data(fields: [DataField('exception', Exception)])
UnexpectedException
}
where:-
@Data()
marks an enum value to be treated as a Data class.
-
One should supply a list of possible fields inside the annotation.
-
If you don't want to add fields, use
@object
annotation instead. -
Fields are supplied in the form of
DataField
objects. -
Each
DataField
must contain thename
and thetype
of the field. -
If the field type needs to be generic use
Generic
type and annotate the enum value with@generic
annotation.
@object
marks an enum value to be treated as an object.
Then it can be used easily with the generated when
function
moviesResponse.when(
success: (data) => print('Total Movies: ${data.movies.totalPages}'),
unauthorized: (_) => print('Invalid ApiKey'),
noNetwork: (_) => print(
'No Internet, Please check your internet connection',
),
unexpectedException: (error) => print(error.exception),
);
from language.
@LinusU great example!
Dart has the idea of a refinement/type test/smart cast. I wonder if this could work similar to how Kotlin does this.
To re-use @LinusU's example:
when (_state) {
is _ChooseProviderState -> ...
is _LoadingState -> ...
is _ChooseAccountsState -> ...
is _ImportingState -> ...
}
Kotlin was originally designed on top of the JVM.
Java doesn't have the idea of smart casting, so this was a significant improvement:
if (_state instanceof _ChooseProviderState) {
_ChooseProviderState inner = (_ChooseProviderState) _state;
...
}
...etc.
Dart already has the idea of smart casting, so I have a feeling that implementing this should not be the most difficult. Agreeing on a syntax will be the fun part 😅
(To steal what @LinusU said) Is there anything I could do to make this possible?
from language.
@LinusU @tchupp I agree that native support for ADTs would be nice and that it's much easier to work with ADTs in Rust/Kotlin/Swift/F# etc!
But at least the problem of not having compiler guarantees can be mostly worked around in Dart.
To continue on the example @LinusU posted above, you can add a method to the common type, _State
, which lets you use it in a type-safe manner, ensuring you don't forget any case, by moving the if/else
chain into a single place (a technique I use in Java normally, but works in Dart too - as long as you keep constructors for the sub-types in the hierarchy private, so they can only be instantiated via the common type via static methods, or within the same file):
abstract class _State {
T use<T>(
T Function(_ChooseProviderState) useChooseProviderState,
T Function(_LoadingState) useLoadingState,
T Function(_ChooseAccountsState) useChooseAccountState,
T Function(_ImportingState) useImportingState) {
if (this is _ChooseProviderState) {
return useChooseProviderState(this);
}
if (this is _LoadingState) {
return useLoadingState(this);
}
if (this is _ChooseAccountsState) {
return useChooseAccountState(this);
}
if (this is _ImportingState) {
return useImportingState(this);
}
throw Exception('Invalid state');
}
}
Now, your build
method looks much nicer:
@override
Widget build(BuildContext context) {
return _state.use(
(chooseProvider) => _buildChooseProvider(context, chooseProvider),
(loading) => _buildLoading(context, loading),
(chooseAccounts) => _buildChooseAccounts(context, chooseAccounts),
(importing) => _buildImporting(context, importing));
}
from language.
@i-schuetz I would say you are correct in that there is a deep conceptual difference. In a class (or struct or record), you have the "multiplication" construct of ADTs. What is lacking in Dart at the moment is the "sum" construct of ADTs. I think the sum is better viewed as something different than a special case of inheritance in OOP, although it can to some extent be "simulated" that way.
Here is a bit old, but still very good article about ADTs:
http://static.v25media.com/the-algebra-of-data-and-the-calculus-of-mutation.html
When you get used to sum types, it feels very limited not having them.
For me, TypeScript's tagged unions are quite lacking because pattern matching is missing, which gives you sort of only the first half of the power/convenience of sum types.
from language.
Yes, I agree that we are missing when
in the example I have, the _when
method is not exhaustive, nor does it catch any additional sub-types added to the parent class with the @sealed
annotation.
For example, adding an isPaused
does not give any IDE or Compiler hints:
class IsPaused extends ViewState {
@override
ViewState reduce(ViewState state) {
return _when(
(isIdle) => print('isIdle'),
(isloading) => print('isloading'),
(success) => print('${success.data}'),
(error) => print('${error.message}'),
);
}
}
What I would like to see if there is an official Dart example of how we ought to be implementing @sealed
? (cc. @srawlins \ @mit-mit)
from language.
@adrianboyko Check again under "Patterns and other features".
from language.
Looks great! I'd suggest using @required
named parameters though, so you don't have to depend on a particular argument order. It also makes it possible to remove subtypes in the future without getting... lots of unreadable errors from the analyzer.
from language.
Everyone wants to solve this problem :D here's a few more existing libraries from #546 (comment)
If existing packages are interesting to see, here's a bunch of them:
from language.
Where this is being worked on or tracked?
We are VERY much heads-down on null-safety at the moment. If you're subscribed to this issue, you'll see updates as they happen.
Thank you. I really want to stay up to date with this.
from language.
I prefer we take a look at the Scala 3:
https://docs.scala-lang.org/tour/case-classes.html
https://docs.scala-lang.org/tour/pattern-matching.html
https://docs.scala-lang.org/overviews/core/value-classes.html#inner-main
https://docs.scala-lang.org/scala3/book/types-union.html
https://docs.scala-lang.org/scala3/book/types-intersection.html
https://docs.scala-lang.org/scala3/reference/enums/enums.html
https://docs.scala-lang.org/scala3/book/types-opaque-types.html#inner-main
from language.
@hepin1989
- Case classes:
- Pattern matching:
- Value classes:
- In a way, Dart already has this with
const
constructors.
- In a way, Dart already has this with
- Types union:
- This issue, #83
- Types intersection:
- This issue and maybe #83 as well, or maybe it's out of scope there.
- Enums:
- A lot of issues tracking enum improvements, did you have something specific in mind?
- Opaque types:
- This can be done today with typedefs and extension methods.
from language.
Very cool @xsahil03x ... the existing solutions to this are probably not as nice.
https://github.com/google/built_value.dart/blob/master/example/lib/enums.dart
https://github.com/werediver/sum_types.dart
Would be nice if one alternative became the standard though.
from language.
True, but as Brian mentioned we have had sealed_unions for nearly 2 years now and other have managed to use them successfully during that time.
However, I am glad to see that the community at large is helping the language.
from language.
hiya, any updates on ADT support? 🙂
from language.
I tend to use https://pub.dev/packages/dartz for functional programming types.
Functional programming in Dart
Type class hierarchy in the spirit of cats, scalaz and the standard Haskell libraries
• Immutable, persistent collections, including IVector, IList, IMap, IHashMap, ISet and AVLTree
• Option, Either, State, Tuple, Free, Lens and other tools for programming in a functional style
• Evaluation, a Reader+Writer+State+Either+Future swiss army knife monad
• Type class instances (Monoids, Traversable Functors, Monads and so on) for included types, as well as for several standard Dart types
• Conveyor, an implementation of pure functional streaming
from language.
This is really awesome for Kotlin developers. You can say the current built-in types are enough for us but ENOUGH is far less than what we need. As a newer language, I believe we can do better in grammar sugar. I miss Kotlin very single time I write Flutter! I want data classes and removing the ;
. And also enum class bring nothing but string and index! I can't say it can not work but I have to say I have to write more branches.
from language.
Very similar to http://github.com/dart-lang/sdk/issues/31253.
from language.
@lrhn There may be similarities in the way in which it will be implemented in Dart, but other than that this is unrelated with data classes.
from language.
@tchupp Is visitor pattern really a replacement? Doesn't it just move the "instance of" checks to the visitor (I may be mistaken, not super familiar with it)?
Also, I wonder whether ADTs are just convenience for inheritance in OOP, or there's a deeper conceptual difference that makes the "instance of" not violate OOP practices in this case. It feels like may be a fundamental difference between if you're expecting Animal
, which can be a Cat
or Dog
or Result
, which can be Ok
or Error
. A manifestation of this may be that in most languages you can't share data between the types in ADTs, while using inheritance you can. Not sure about this take.
Another take could be that the the "ADT" is just convenience to do do instance of checks in a type safe and concise way. Making the if
s exhaustive, in combination with smart casting, would be no different than just having "ADT"s, except with an uglier syntax.
from language.
Hey @kevmoo any plans on this?
from language.
CC @munificent
from language.
@sealed
annotation is now available as of meta ^1.1.7
merged in on #11 => meta.dart#L202
from language.
How does this example look?
(Comments, correct alternative implementations welcomed)
https://gist.github.com/nodinosaur/adf4da0b5f6cddbdcac51c271db56465
from language.
ADTs as a whole can mostly be represented in dart currently using inheritance. The only thing that's limiting those approaches' success is their verbosity. However, I think this would largely be alleviated by the addition of data classes and the addition of a when
construct which can be an extension of a switch
block.
Take the Kotlin code in the OP, for example. If Dart had data classes and a when
construct, the Dart equivalent of the Kotlin code might look something like this:
abstract data class LoginResponse;
data class Success(String authToken) extends LoginResponse;
data class InvalidCredentials extends LoginResponse;
data class NoNetwork extends LoginResponse;
data class Unexpected(Error error) extends LoginResponse;
// ...
on(LoginResponse loginResponse) {
when (loginResponse) {
is Success:
// direct access to auth token available w/o need to cast
return loginResponse.authToken;
is InvalidCredentials:
return null;
is NoNetwork:
return null;
is Unexpected:
return loginResponse.error;
}
}
If Dart gets support for pattern matching in switch blocks, this might not even need a dedicated when
construct as the type matching can be made in the case statements.
The only thing this doesn't cover is the exhaustive type matching, but IMO I'm not sure that's a bad thing. Having exhaustive type matching is nice for type safety or testing, but it also cuts into the ability of a type being extensible.
from language.
@sealed
just means the class isn't supposed to be extensible, i.e. it can't be the superclass to any other class. The ADT behavior of sealed classes in Kotlin just takes advantage of the fact that sealed classes in Kotlin can have child classes made, but they must be in the same file as the sealed class. (So, in reality, Kotlin is also using inheritance to achieve ADT-like behavior. There are just a couple more features Kotlin offers [like exhaustive type checking] to make these "ADTs" more tidy to use overall.)
from language.
Another alternative is https://github.com/factisresearch/sum_data_types. It offers both sum- and product-types. Unfortulately the Syntax for the constructors is a bit cumbersome.
from language.
Yes, ultimately that is what we all want & hopefully we won't be waiting too much longer :)
from language.
Where this is being worked on or tracked?
from language.
Where this is being worked on or tracked?
We are VERY much heads-down on null-safety at the moment. If you're subscribed to this issue, you'll see updates as they happen.
from language.
well, I guess technically Dart has some of this implemented on try catch clause.
I guess what we can do now is use NNBD and feedback them so it can go stable soon.
from language.
for the time being, a visitor can also be used to get compile-time errors about missing cases.
abstract class ResponseVisitor<R> {
R visitSuccess(_SuccessResponse response);
R visitFailure(_FailureResponse response);
}
abstract class _Response {
R accept<R>(ResponseVisitor<R> visitor);
}
class _SuccessResponse implements _Response {
_SuccessResponse(this.data);
final int data;
@override
R accept<R>(ResponseVisitor<R> visitor) {
return visitor.visitSuccess(this);
}
}
class _FailureResponse implements _Response {
_FailureResponse(this.message);
final String message;
@override
R accept<R>(ResponseVisitor<R> visitor) {
return visitor.visitFailure(this);
}
}
class ServiceClass {
_Response _fetchData() {
try {
return _SuccessResponse(42);
} on Exception {
return _FailureResponse('No data found');
}
}
void getData(ResponseVisitor visitor) {
_fetchData().accept(visitor);
}
}
class WorkerClass implements ResponseVisitor<void> {
WorkerClass(this.service);
final ServiceClass service;
@override
void visitFailure(_FailureResponse response) {
print('Error happened: ${response.message}');
}
@override
void visitSuccess(_SuccessResponse response) {
print('Result arrived: ${response.data}');
}
void doWork() {
service.getData(this);
}
}
void main() {
final service = ServiceClass();
WorkerClass(service).doWork();
}
from language.
Thanks @munificent I'm certainly very much appreciate all the efforts the Dart team is putting in for NNBD! And NNBD already makes the "enums+extensions with switch
's" pattern so much more useful for the kind of things that I'm used to using sealed classes.
And in the meantime I spotted some new lints in Dart 2.9 that I think might help with some of the uses described here until there is built in language support: exhaustive_cases and no_default_cases.
from language.
Related to #83
from language.
Does Algebraic DT include monads like Either?
from language.
@bubnenkoff check this pub https://pub.dev/packages/result_type it might help.
from language.
from language.
Sum types are not yet in the language funnel :(
from language.
It's great we are getting fields in enums in the next release!
That was released in Dart 2.17 a few days ago. See the announcement: https://medium.com/dartlang/dart-2-17-b216bfc80c5d
from language.
Related Issues (20)
- Destructuring records in functions HOT 5
- Lower the directory depth cost of packages HOT 2
- Can an augmentation library be an entry point? HOT 9
- How do we merge augmentation imports? HOT 6
- Grammar rule adjustments for augmentation libraries HOT 1
- False analyzer warnings when using nullable extensions HOT 2
- Augmentation libraries can't be main libraries as well? HOT 2
- Augmenting declarations cannot occur outside augmentation libraries, right? HOT 4
- Proposal: remove special analyzer behavior for await expressions with "null context". HOT 5
- Proposal: remove special front end behavior for await expressions with context `dynamic`. HOT 2
- Proposal: align front end behavior with analyzer for if-null expressions in context `dynamic`. HOT 3
- Support debugging Dart projects with compilation errors HOT 3
- Pattern matching allows you to refer to its own scope HOT 3
- Proposal: add a context for RHS of equality operations. HOT 1
- matching default values in switch statements (with record patterns) HOT 4
- Failing analysis of macro test prevents other actions HOT 1
- [augmentation-libraries] Missing grammar rule for extension and enum augmentations? HOT 6
- Assignment expressions in `if`-statements don't correctly promote nullable variables HOT 4
- Eliminate symbol literals with several identifiers? HOT 10
- Update the spec parser to take stop and continuation tokens into account for `<typeArguments>` as a selector
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from language.