โ€ข

newsletter

Advanced Dart to build Effect: fpdart v2

Learn many powerful and advanced dart's features required to build an Effect System in dart: type system (Never, void, Null), type inference, FutureOr for synchronous and asynchronous functions.


Sandro Maglione

Sandro Maglione

Software development

You don't know the details of a library until you implement them yourself ๐Ÿค“

This week first preview release of fpdart v2, with the new Effect class.

As I am working on the API I am learning more and more about the details of how an Effect System works, and how to adapt it to dart.

Get ready to dive into advanced stuff here, let's go ๐Ÿ‘‡


Wrestle with the type system (and it's limitation)

This is an "Effect" in fpdart:

  • E: Dependencies/Environment
  • L: Errors
  • R: Success
abstract class IEffect<E, L, R> {
  const IEffect();
  Effect<E, L, R> get asEffect;
}

Now, Either and Option are also IEffect. They both don't have dependencies (no E).

How can the dart type system cope with that? Three options:

  • Never
  • void
  • Null
sealed class Option<R> extends IEffect<Never, Never, R>
sealed class Either<L, R> extends IEffect<Never, L, R>

Turns out this is "okay" for Option and Either, but terrible for a custom Effect.

This is a limitation of dart itself.

It has to do with Statically checked declaration-site variance ๐Ÿ’๐Ÿผโ€โ™‚๏ธ

I opened an issue on the dart language repository to discuss and understand this better ๐Ÿค”

Solution (for now): Use Null or a generic type E for dependencies in your own Effect instances.

abstract interface class HttpClient {
  Effect<Null, PokemonError, String> get(Uri uri);
}

Dependencies (at scale)

In effect (typescript) it's possible to use union types to infer dependencies when using Effect.gen.

No so in dart ๐Ÿ˜•

Dart doesn't have union types and type inference for dependencies is not possible:

/// You are expected to define the return type manually (no inference) ๐Ÿ‘๐Ÿ‘Ž
Effect<http.Client, HttpError, http.Response> get(
  Uri url, {
  Map<String, String>? headers,
}) =>
    Effect.gen(($) async {
      /// ...
    });

How to solve this? Still looking for a good solution here ๐Ÿค”

  • Define dependencies as dart's Record
  • Use class parameters
  • Provide dependencies in advance

Keep looking ๐Ÿ‘€

Sync/Async together

Dart has something called FutureOr.

FutureOr is the only union type in dart, it can be a Future (async) or a normal function (sync)

Effect can run both sync and async: it uses FutureOr to achieve this ๐Ÿ‘‡

Effect<http.Client, HttpError, http.Response> get(
  Uri url, {
  Map<String, String>? headers,
}) => Effect.gen(($) async {
      final client = $.sync(Effect.env());

      final response = await $.async(Effect.tryCatch(
        execute: () => client.get(url, headers: headers),
        onError: (_, __) => const RequestError(),
      ));

      if (response.statusCode != 200) {
        return $.sync(Effect.fail(const ResponseError()));
      }

      return response;
    });

FutureOr is something special in dart, and therefore it requires some "special attention", especially for error management.

Read more about what we may call asyncOr in dart ๐Ÿ‘ˆ

Best way to learn: Do

effect in typescript offers an extensive API and many modules.

I explored them in the past, but only now I am getting a real sense of how things work.

I need to know the details to make it work in dart.

This requires reading and understanding all the internal code ๐Ÿ‘€

At the end of this journey I will have a better understanding of the details. I will share everything in more tutorials, projects, and contributions ๐Ÿ”œ


Want to learn a language? Find an interesting project, and dive all in!

You will hit edge cases, search solutions everywhere (documentation, Github issues, API reference), and you will come out on the other side with a new level of programming-wisdom ๐Ÿค“

See you next ๐Ÿ‘‹

Start here.

Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.