dart-lang / language

Design of the Dart language
Other
2.65k stars 201 forks source link

Algebraic Data Types (ADTs, Sealed Classes, Enum with associated values) #349

Closed ZakTaccardi closed 1 year ago

ZakTaccardi commented 6 years ago

ADTs are a very important part of any modern programming language.

Imagine you have a network call that submits login credentials. This call can:

In Kotlin, the return value could be represented as the following sealed class

sealed class LoginResponse {
    data class Success(val authToken) : LoginResponse()
    object InvalidCredentials : LoginResponse()
    object NoNetwork : LoginResponse()
    data class UnexpectedException(val exception: Exception) : LoginResponse()
}

Kotlin's when statement unwraps this beautifully, smart casting LoginResponse.

fun on(loginResponse: LoginResponse) {
    return when (loginResponse) {
        is LoginResponse.Success -> {
            // direct access to auth token available w/o need to cast
            loginResponse.authToken
        }
        is LoginResponse.InvalidCredentials -> {

        }
        is LoginResponse.NoNetwork -> {

        }
        is LoginResponse.Unexpected -> {
            // direct access to exception available w/o need to cast
            loginResponse.exception
        }
    }
}

Sadly, languages like Java and Dart are unable to elegantly express this concept (ADTs). For a best case example, checkout out Retrofit's Response object. It is a single pojo that contains both:

Both these fields are nullable, but one must be non-null and the other must be null. To know which field can be accessed, you have to check the .isSuccessful() flag.

Now imagine the verbosity if this best case scenario has to scale to 4 or more possible results. A whole new enum class would have to be added to work around it...it's not pretty.

Please add support for ADTs to Dart.

note: As an Android developer, I find that Flutter is an interesting proposition that is currently held back by the language.

avilladsen commented 6 years ago

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.

ZakTaccardi commented 6 years ago

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

avilladsen commented 6 years ago

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?

brianegan commented 6 years ago

Hey @avilladsen --

The features I would like (whether it's sealed classes or union types):

  1. 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.
  2. 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".
  3. Concise syntax without ==, toString, and hashcode and copy 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.
  4. 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

pulyaevskiy commented 6 years ago

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.

lrhn commented 5 years ago

Very similar to http://github.com/dart-lang/sdk/issues/31253.

jarlestabell commented 5 years ago

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.

derekdreery commented 5 years ago

Apologies if this is a bit of a +1 response, but coming from Rust I really miss ADTs (called enums 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
    }
}
ivnsch commented 5 years ago

@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.

tchupp commented 5 years ago

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.

ivnsch commented 5 years ago

@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 ifs exhaustive, in combination with smart casting, would be no different than just having "ADT"s, except with an uglier syntax.

jarlestabell commented 5 years ago

@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.

Aetet commented 5 years ago

Hey @kevmoo any plans on this?

LinusU commented 5 years ago

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?

kevmoo commented 5 years ago

CC @munificent

tchupp commented 5 years ago

@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?

renatoathaydes commented 5 years ago

@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));
  }

Dart Pad Full Example

ds84182 commented 5 years ago

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.

thosakwe commented 5 years ago

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)
nodinosaur commented 5 years ago

@sealed annotation is now available as of meta ^1.1.7 merged in on #11 => meta.dart#L202

nodinosaur commented 5 years ago

How does this example look? (Comments, correct alternative implementations welcomed)

https://gist.github.com/nodinosaur/adf4da0b5f6cddbdcac51c271db56465

andrewackerman commented 5 years ago

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.

nodinosaur commented 5 years ago

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)

andrewackerman commented 5 years ago

@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.)

xsahil03x commented 4 years ago

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.

@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),
  );
renatoathaydes commented 4 years ago

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.

tvh commented 4 years ago

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.

renatoathaydes commented 4 years ago

Everyone wants to solve this problem :D here's a few more existing libraries from https://github.com/dart-lang/language/issues/546#issuecomment-525382550

If existing packages are interesting to see, here's a bunch of them:

nodinosaur commented 4 years ago

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 :)

satvikpendem commented 4 years ago

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.

flukejones commented 4 years ago

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.

nodinosaur commented 4 years ago

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.

flukejones commented 4 years ago

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".

nodinosaur commented 4 years ago

Yes, ultimately that is what we all want & hopefully we won't be waiting too much longer :)

pedromassango commented 4 years ago

Where this is being worked on or tracked?

kevmoo commented 4 years ago

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.

pedromassango commented 4 years ago

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.

xuanswe commented 4 years ago

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#.

jodinathan commented 4 years ago

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.

veggiedefender commented 4 years ago

hiya, any updates on ADT support? :slightly_smiling_face:

pedromassango commented 4 years ago

hiya, any updates on ADT support? :slightly_smiling_face:

The Dart team is focused in NNBD now. All we can now is to thumbs up this issue and keep an eye in here.

SzunTibor commented 4 years ago

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();
}
munificent commented 4 years ago

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.

maks commented 4 years ago

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.

pedromassango commented 3 years ago

Related to https://github.com/dart-lang/language/issues/83

bubnenkoff commented 3 years ago

Does Algebraic DT include monads like Either?

minikin commented 3 years ago

@bubnenkoff check this pub https://pub.dev/packages/result_type it might help.

nodinosaur commented 3 years ago

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

bubnenkoff commented 3 years ago

thanks! But is there any plan to add something similar to Dart to get ADT out of the box?

чт, 4 февр. 2021 г. в 12:50, George.M notifications@github.com:

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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dart-lang/language/issues/349#issuecomment-773177887, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABRWNFXZ5PVZQEAIFZLHB5DS5JUUXANCNFSM4HMFEFPQ .

flukejones commented 3 years ago

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.