Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All properties available on all factory methods #60

Closed
atreeon opened this issue Feb 21, 2020 · 6 comments
Closed

All properties available on all factory methods #60

atreeon opened this issue Feb 21, 2020 · 6 comments
Assignees
Labels
enhancement New feature or request

Comments

@atreeon
Copy link

atreeon commented Feb 21, 2020

(great job Remi!)

I wasn't sure how to title this! It isn't possible to do the following because the properties are only available if all factory methods have that property. How is it best to overcome this?

abstract class Result with _$Result {
  const factory Result.Continue(Action action) = Result_Continue;
  const factory Result.Retry(Action action) = Result_Retry;
  const factory Result.Complete() = Result_Complete;
}

blah(Result result){
if(result is Result_Continue || result is Result_Retry){
	process(result.action);  //. <----- this is not possible because action is not on all factorys
}

I thought that composition might do it, is that the best way? Is it just a different way of thinking than my inheritance conditioned brain?

@freezed
abstract class ResultType with _$Result {
  const factory ResultType.Continue(Action action) = ResultType_Continue;
  const factory ResultType.Retry(Action action) = ResultType_RetryError;
}

@freezed
abstract class Result with _$Result {
  const factory Result.ResultType(ResultType resultType) = Result_Type;
  const factory Result.Complete(WadFile wadFile) = Result_Complete;
}

blah(Result result){
if(result is Result_Type){
	process(result.resultType.action);
}
@rrousselGit rrousselGit added the enhancement New feature or request label Feb 21, 2020
@rrousselGit
Copy link
Owner

rrousselGit commented Feb 21, 2020

That is currently not supported.
But, it is not impossible to support it.

A more complex case I would like to fully support is:

abstract class Complex with _$Complex {
  const factory Complex.a(int value) = ComplexA;
  const factory Complex.b(double value) = ComplexB;
}

I'm thinking of a few things for these cases:

Complex example = Complex.a(42);

num value = example.value; // fine
double value = example.value // implicit-cast error, value is a num not a double

example.value would be num because that is the nearest common type between double and int

Then one more difficult scenario would be:

abstract class ReallyComplex with _$ReallyComplex {
  const factory ReallyComplex.a(int variable) = ReallyComplexA;
  const factory ReallyComplex.b(double variable) = ReallyComplexB;
  const factory ReallyComplex.c() = ReallyComplexC;
}

In which case I think I'd go with:

ReallyComplex example;

example.$variable(
  (num variable) => print(variable),
  orElse: () => print('nothing'),
);

The $ being necessary so that we can still do:

ReallyComplex example;
if (example is ReallyComplexA) {
  print(example.value);
} 

@atreeon
Copy link
Author

atreeon commented Feb 21, 2020

what would that look like for my example?

abstract class Result with _$Result {
  const factory Result.Continue(Action action) = Result_Continue;
  const factory Result.Retry(Action action) = Result_Retry;
  const factory Result.Complete() = Result_Complete;
}

result.$variable(
  (Action action) => process(action),
  orElse: () => print('nothing'),
);

and if I wanted to differentiate between continue and retry I would have to vary my type (ActionContinue or ActionRetry or something like that)

abstract class Result with _$Result {
  const factory Result.Continue(ActionContinue action) = Result_Continue;
  const factory Result.Retry(ActionRetry action) = Result_Retry;
  const factory Result.Complete() = Result_Complete;
}

result.$variable(
  (ActionContinue action) => process(action),
  (ActionRetry action) => wait 5.then(process(action)),
  orElse: () => print('nothing'),
);

...if I have that right then I think that would work well. It would be nice to vary by the continue and Retry types though and not by the parameters. In my example an Action is always an Action but the Result is the type that determines whether an action should Continue or Retry), maybe that is impossible as everything works around the parameters and not the types? but, if the syntax is possible, maybe an as keyword could work?

abstract class Result with _$Result {
  const factory Result.Continue(Action action) = Result_Continue as ResultAction;
  const factory Result.Retry(Action action) = Result_Retry as ResultAction;
  const factory Result.Complete() = Result_Complete;
}

result.maybeWhen(
  ResultAction: (action) => process(action,
  orElse: () => 'fallback',

or

result.maybeWhen(
  Result_Continue: (action) => process(action),
  Result_Retry: (action) => retry(action),
  orElse: () => 'fallback',

@rrousselGit
Copy link
Owner

An alternative is to make the properties that are not shared between all constructors nullable.

Since Dart will "soon" have non-nullable types, it should be compile safe.

Freezed could still generate that$my_property variant for more complex cases.

@atreeon
Copy link
Author

atreeon commented Feb 24, 2020

I haven't thought this through that much (!) but could you go for a ducktyping approach? In my example above all I really want to do is run code wherever I have a property called action of type Action.

abstract class Result with _$Result {
  const factory Result.Continue(Action action) = Result_Continue;
  const factory Result.Retry(Action action, int retryTime) = Result_Retry;
  const factory Result.Complete() = Result_Complete;
}

main() {
  var result = Result_Continue(Action());

  result.maybeWhen(
    action: () => print("action"),
    action_retryTime: () => print("action in retrytime"),
    retryTime: () => print("retry time")
    _void: () => print("everything "),
  );
}

vary by the property names rather than the types because an int may not be descriptive enough - age & height for example could be two different parameters

@CodingSoot
Copy link

A more complex case I would like to fully support is:

abstract class Complex with _$Complex {
  const factory Complex.a(int value) = ComplexA;
  const factory Complex.b(double value) = ComplexB;
}

For this specific case, we just have to add an abstract getter with the nearest common type :

abstract class Complex with _$Complex {
  const factory Complex.a(int value) = ComplexA;
  const factory Complex.b(double value) = ComplexB;

  num get value;
}

@rrousselGit rrousselGit self-assigned this May 10, 2023
@rrousselGit
Copy link
Owner

Closing in favor of #907, which enables more advanced class hierarchies.

Alternatively, you can use @Implements/@With to do things by hand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants