spebbe / dartz

Functional programming in Dart
MIT License
749 stars 60 forks source link

[feature request] If Either<Failure, Success>, how can I know what kind of Failure happened. #119

Open JCKodel opened 1 year ago

JCKodel commented 1 year ago

Scenario:

What about:

static void _dispatchCommand({required BuildContext context, required Command command}) {
  final result = command.dispatch(context);

  result.leftMap(
    (failure) {
      final errorMessage = match<Failure, String>(failure)
          .when<GetNumberedQuoteCommandEmptyNumberFailure>((f) => "Number should not be empty")
          .when<GetNumberedQuoteCommandInvalidNumberFormatFailure>((f) => "Number is not correctly formatted")
          .when<NumberShouldBePositiveFailure>((f) => "Number should be positive")
          .when<NumberShouldBeLessThan100Failure>((f) => "Number should be < 100")
          .when<NumberOutOfRangeFailure>((f) => "Number should be between ${f.minValue} and ${f.maxValue}")
          .otherwise((f) => "Unknown ${f} error");

      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(errorMessage),
        ),
      );
    },
  );
}

In the above code, I just ignore the Success (right), because, well, I don't need to inform the user about it, but for Failure (left), I can test it using generics and have typed failures to create my error message string (notice how I interpolate the minValue and maxValue in the error message).

How?

Using this code (based on #48):

extension Matching<L, R> on Either<L, R> {
  Either<L, R> on(bool Function(R r) predicate, L Function(R r) value) => flatMap((r) => predicate(r) ? left<L, R>(value(r)) : right<L, R>(r));
  Either<L, R> when<T extends R>(L Function(T r) value) => flatMap((r) => r is T ? left<L, R>(value(r)) : right<L, R>(r));
  L otherwise(L Function(R r) transformation) => fold(id, transformation);
}

Either<Result, A> match<A, Result>(A a) => right(a);

So now I can filter my result (by bool predicate), or I can match types using my when extension.

Anyway, my feature request is to incorporate that when, on and otherwise into Either<L, R>.

jacksonb-cs commented 11 months ago

It sounds like your problem could easily be solved by making Failure a sealed class and using a switch statement. But, if you don't want to do that, you could add an abstract method to Failure, which would require all implementing classes to override said method.

abstract class Failure {
  String getFailureMessage();

  ...
}

class NumberOutOfRangeFailure extends Failure {
  final int minValue = ...;  // Get these values however you want
  final int maxValue = ...;

  @override
  String getFailureMessage() {
    return "Number should be between ${minValue} and ${maxValue}";
  }
}

Then you can just use any instance of a subclass of Failure to call getFailureMessage().