How to map an Either to a Future in fpdart

26 January 2022

5 min read

Functional programming

Recently, I have been asked in an issue on the repo of fpdart this question:

I have a function that returns Future and I am using async/await. Inside that function, I have an Either. [...] I tried to use map on the Either, but what I do on both paths is async, [...] I can't await on either.map.

What should be the suggested way to use option/either .map in conjunction with await?

This is a legitimate question. You will find yourself in those kind of situations often while working with functional programming. So, what is the answer here?

Sync VS Async

You should reprogram your mind to think sync and async (Future/Promise) as two different worlds.

You cannot map an Either with a function that returns a Future. That is because Either expects its world to be sync.

In a sync world everything happens at once. There is no need to wait for anything. More importantly, there is no interaction with the “outside world“.

This means that, unless our server explodes or you are so dumb to introduce a bug in your application (how could you do that 💁🏼‍♂️), everything should work.

No error 500, no missing data, no typing error. Nothing of that sort.

Entering async

When we are working async everything is much more unpredictable. Anything can (and will!) happen.

Not only that, but you will also get the result from the function in the future (it is called Future after all).

SYNC

OptionEitherIO

ASYNC

TaskOptionTaskEitherTask

This situation introduces a whole new level of complexity and a completely different API. Well, that is the same for fpdart and functional programming!

Why I cannot map an Either with Future

Coming back to the original question, why can't you map an async function from an Either?

When you call map on Either, you expect to immediately convert the value inside the Either. Well, this simply cannot happen when we are dealing with Future.

Furthermore, executing a Future would make the function impure. That is because the result of calling the function will change every time you execute it. Again, that is because the "outside world" is unpredictable.

Practically, it means that you lose all the advantages of a pure function. In functional programming, no thanks.

If your function becomes async, you cannot come back, everything enters this new context. What color is your function?

Solution - What to do instead?

When dealing with the "outside world" in functional programming, we want to defer the execution to a later stage in our codebase.

That is what happens with Haskell for example. You have your main entry point, which is the only function that is allowed to execute any code. All the other part of the codebase are simply descriptions of what should happen at any step.

The key here is the word description. We want to tell our application what to do in every possible situation (in this case the Left and Right case of Either), without actually executing anything until the end.

Subscribe to the newsletter

In my newsletter I share ideas, tutorials, articles, and code snippets about Functional Programming and Frontend Development. I work both on web and mobile, using Typescript + React and Dart + Flutter.

TaskEither

In order to achieve this with Either, we must move from a sync world to an async world before mapping.

Either of fpdart provides a toTaskEither method. This function allows to convert Either to TaskEither (sync to async).

TaskEither is the exact same thing of Either, but it lives in the async world. Simply take a look at the signature of the value contained in both Either and TaskEither:

either.dart
/// Either (`Right`)
final R _value;
...
/// Either (`Left`)
final L _value;

As you can see below TaskEither is just an async abstraction over Either. It wraps Either in a Future:

task_either.dart
/// TaskEither
final Future<Either<L, R>> Function() _run;

Give me an example

Let us see an example.

You have your beautiful (and type-safe) application. You are smart, so you call validate to check the validity of your data before making any async request.

Your next requirement is to call the sendComplainRequest endpoint if the data is not valid (used to complain with management).

Whereas you should call the everythingIsFine endpoint if the data is valid (🚀).

We must move from the land of our safe sync app (Either) to the insidious external word (API aka Future).

In order to do that, we call toTaskEither to move from sync to async. Then we map over the Left and Right side using flatMap and orElse.

sync_to_async.dart
import 'package:fpdart/fpdart.dart';

Future<int> everythingIsFine(int a) async => a + 42;

Future<String> sendComplainRequest(String a) async =>
    '$a - What data is that!!!';

Either<String, int> validate() => Either.of(10);

void main() {
  /// You have an [Either]. Now, suddenly a [Future] appears!
  /// What do you do?
  ///
  /// You need to change the context, moving from a sync [Either]
  /// to an async [TaskEither]! Simply use `toTaskEither`.
  final eitherToTaskEither = validate()
      .toTaskEither()
      .flatMap(
        (r) => TaskEither(
          () async => Either.of(
            await everythingIsFine(r),
          ),
        ),
      )
      .orElse(
        (l) => TaskEither(
          () async => Either.left(
            await sendComplainRequest(l),
          ),
        ),
      );
}

Let me know if this post was helpful, and reach out on Twitter for any clarification.