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.
๐ fpdart v2 pre-release is out ๐ You can now try out the new fpdart v2 with the new Effect ๐ฅ Next step full documentation, tests, and more breaking changes of course ๐๏ธ
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/EnvironmentL
: ErrorsR
: 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 typeE
for dependencies in your ownEffect
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 aFuture
(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 ๐