fpdart
fpdart copied to clipboard
fpdart v2.0.0
This PR is an open discussion on what's the best strategy to release fpdart v2 to minimize friction for users of the library
π Please comment below with any idea, comment, critique or opinion π
Problems with v1
Too many classes (IO
, IOOption
, IOEither
, Task
, TaskOption
, TaskEither
, Reader
, State
, ReaderTaskEither
):
- Similar implementation with different generic parameters = A lot of duplicated code (both core and tests)
/// [IO] π
abstract final class _IOHKT {}
final class IO<A> extends HKT<_IOHKT, A>
with Functor<_IOHKT, A>, Applicative<_IOHKT, A>, Monad<_IOHKT, A> {
final A Function() _run;
}
/// [Task] π
abstract final class _TaskHKT {}
final class Task<A> extends HKT<_TaskHKT, A>
with Functor<_TaskHKT, A>, Applicative<_TaskHKT, A>, Monad<_TaskHKT, A> {
final Future<A> Function() _run; /// π Difference: [Future] here
}
- Code duplication = Hard to maintain, more lines of code, more code to read (and understand) for contributors
- Requires conversion between classes (
from*
,to*
, e.g.toTask
,toTaskEither
) - Requires having a different
Do
constructor for each class, making the do notation harder to use - Hard to understand for newcomers, hard to reason with and explain for veterans (and verbose)
- More complex code, less contributors
Too much jargon: methods and classes are using terms from pure functional programming (math), less clear and hard to understand (e.g. pure
, Reader
, chainFirst
, traverse
, Endo
).
Typeclasses do not work well with dart and they cause a lot of overhead to maintain and understand. In fact, they are not necessary to implement the core of fpdart (they can be removed ππΌββοΈ).
Too many "utility functions" that may be considered outside of the scope of fpdart (e.g. predicate_extension.dart
).
fpdart v2 solution: Effect
A single Effect
class that contains the API of all other classes in v1 (similar to ReaderTaskEither
).
All Effect-classes derive from the same interface IEffect
:
abstract interface class IEffect<E, L, R> {
const IEffect();
Effect<E, L, R> get asEffect;
}
Benefits
- A lot less code: easier to maintain, contribute, test, understand (a single
effect.dart
) - No need of conversion methods (a lot less code)
- A single Do notation (implemented as a factory constructor
factory Effect.gen
): the do notation also includesOption
andEither
(since both extendIEffect
) - No more jargon: easy to understand method names instead of fp jargon (e.g.
succeed
instead ofpure
) - Removed all typeclasses and unnecessary utility methods
- Easier to explain and understand (focus on learning a single
Effect
and how it works) - Smaller API that allows all the same functionalities as before
- More resources to focus on better documentation, tests, and examples
Important:
Effect
comes from theeffect
library (typescript), which itself was inspired fromZIO
.The
Effect
class and methods infpdart
are based oneffect
from typescript (similar API and methods names).Huge thanks also to @tim-smart for his initial
zio.dart
implementation.
Downsides
- β οΈ Huge breaking change β οΈ
- Nearly all tests need to be rewritten
- Documentation and examples to redo completely
Would love to implement these changes in my existing project. I've been using this package for quite some time, and I've built my Flutter App's architecture with the combination fp_dart and OOPs concepts. So even though this change will be drastic at first to implement, it will become smooth process at the end of it, thus I won't mind migrating my codebase to fp_dart package's new methods.
Released the first pre-release: fpdart v2.0.0-dev.1
Hello! Thanks for your mission. But I missed rightsEither(). for me it's good function for safety data load
Thanks!
I have questions. Should I rewrite code? Now match only in Effect I used match with Options and Either like this
data.match( () => const Text('not found'), (final data) => Widget(data: data), ),
Excited to see this in flight.
Question: is there a reason not to match the type signature introduced by the typescript library, in terms of type parameter names and their ordering?
It might be easier to bring the community up-to-speed if you can lean a little harder on the existing effect ecosystem. With the type parameter discrepancies, I find myself getting confused jumping back and forth between the two systems (i.e., is R "Right" or "Requirements"? is E "Error" or "Environment"?)
I can understand how things ended up here, and maybe there's good reason to keep it this way, given how the type parameters are named in the context of Either.
I would like to try v2 of this package, but the version constraint of the meta package conflicts with the stable version of Flutter. If there are no issues, how about setting the meta version to 1.11.0?
Resolving dependencies...
Note: meta is pinned to version 1.11.0 by flutter_test from the flutter SDK.
See https://dart.dev/go/sdk-version-pinning for details.
Because every version of flutter_test from sdk depends on meta 1.11.0 and fpdart >=2.0.0-dev.1 depends on meta ^1.12.0, flutter_test from sdk is incompatible with fpdart >=2.0.0-dev.1.
Not good enough in fp to review (and understand) the changes but I can give a hand by trying new version before official release if needed.
Yet I agree with your observations regarding too much jargon
, Hard to understand for newcomers, hard to reason with and explain for veterans
.
I think a lot of us are already convinced about the advantages of using FP in our code, the problem is about converting others, our coworkers first of all. So anything making it easier to enter this paradigm is welcomed (not that you have not already done more than the majority), and I think most of us will not mind having to refactor some code.
Thank for your work :)
New release: fpdart v2.0.0-dev.2:
- Complete
Option
andEither
API - Execute
Effect
usingprovide
(withNull
as dependency) - Fixed implementation of running
Effect
and catchingCause
- Added interruption (
Cause.Interrupted
)-
Deferred
-
Context
- New methods (
raceAll
,race
,delay
,sleep
,timeout
)
-
Error stack not so good. Only show main function call, not show when happen
I don't know it's right or not two Fail just for test if (!sourcePath.isEmpty) { Effect.fail(SourceTypeFailure(error: 'empty source')); }
final sourceType = $.sync(sourcePathType().mapEnv((final env) => sourcePath));
if (!sourcePath.isEmpty) { Effect.fail(SourceTypeFailure(error: 'empty source2')); }
console log:
flutter: empty source on null flutter: empty source2 on null
Hey Sandro.
Looking good, I've been playing around.
Given
typedef ProduceEffect<State, ProductionError, Event>
= Effect<State, ProductionError, Iterable<Event>>;
typedef HandleEffect<State, HandlerError, Event>
= Effect<(State, Event), HandlerError, State>;
typedef ApplyEffect<State,Error,Event> = Effect<State, Error, (Iterable<Event>, State)>
, how would you do a fold across the iterable result of a ProduceEffect using a HandleEffect to result in an Effect of type ApplyEffect?
Feel free to suggest changes in the shapes of those definitions if you have a better starting approach.
Thanks
Hey Sandro.
Looking good, I've been playing around.
Given
typedef ProduceEffect<State, ProductionError, Event> = Effect<State, ProductionError, Iterable<Event>>; typedef HandleEffect<State, HandlerError, Event> = Effect<(State, Event), HandlerError, State>; typedef ApplyEffect<State,Error,Event> = Effect<State, Error, (Iterable<Event>, State)>
, how would you do a fold across the iterable result of a ProduceEffect using a HandleEffect to result in an Effect of type ApplyEffect?
Feel free to suggest changes in the shapes of those definitions if you have a better starting approach.
Thanks
Also, curious if you have any general suggestions in naming type alias of an effect. Those names aren't the names I'm using verbatim, but I have been applying a naming convention where I prefix an alias with a verb matching the general action within the Effect that maps an Env value to the result and/or error it could produce. So for example, in the actual code I'm working on, I've implemented typedefs as such:
typedef DispatchEffect<State, DispatchError, Event>
= Effect<State, DispatchError, Iterable<Event>>;
typedef StateEventHandlerEffect<State, HandlerError, Event>
= Effect<(State,Event), HandlerError, State>;
Again, not 100% sure I'm shaping things here, maybe with this you'll be able to let me know if I'm applying the general use correctly.
Thanks
@SandroMaglione what editor do you use? In intelliJ, when I'm creating an Effect using Effect.gen
, I lose type-awareness inside the call to sync (even if I explicitly supply the type parameter A).
Wondering if you might consider this alternative definition for EffectGen:
class EffectGen<E, L> {
EffectGen({
required FutureOr<A> Function<A>(IEffect<E, L, A>) async,
required A Function<A>(IEffect<E, L, A>) sync,
})
: this.sync = sync,
this.async = async;
FutureOr<A> async<A>(IEffect<E, L, A>effect) => this.async(effect);
A sync<A>(IEffect<E, L, A>effect) => this.sync(effect);
}
@ryanhanks-bestow could you provide a screenshot of IntelliJ that shows the type not inferred? Do you know of any limitation of IntelliJ regarding Records?
Hello! Is there anything I could do to help make some progress on this? I'd really like to switch some code to FPDart, but I don't want to if it'll soon need to be rewritten. In addition, I really like Effect-TS and would love to see a Dart equivalent. I'm certainly happy to help with tests and docs, and would be willing to work through porting implementations.
Hello! Is there anything I could do to help make some progress on this? I'd really like to switch some code to FPDart, but I don't want to if it'll soon need to be rewritten. In addition, I really like Effect-TS and would love to see a Dart equivalent. I'm certainly happy to help with tests and docs, and would be willing to work through porting implementations.
The initial implementation is there. The main issue now is with composition. The dart language doesnβt support union types and type inference is limited. This means that you are required to specific manually all types, which is not ideal from a developer UX perspective. So much so that it doesnβt yet justify using fpdart's Effect
over usual OOP dart patterns.
This may be solved by static metaprogramming, so I am actually waiting for some progress on the language.
The initial implementation is there. The main issue now is with composition. β¦ This may be solved by static metaprogramming, so I am actually waiting for some progress on the language.
Ok then. Thank you for the explanation, it's very helpful for understanding the current progress. Would you expect fpdart 2 to be stable enough to use then, just with the expectation that it'll get more concise in the future, or do you anticipate macros to entirely subsume the current API?
The initial implementation is there. The main issue now is with composition. β¦ This may be solved by static metaprogramming, so I am actually waiting for some progress on the language.
Ok then. Thank you for the explanation, it's very helpful for understanding the current progress. Would you expect fpdart 2 to be stable enough to use then, just with the expectation that it'll get more concise in the future, or do you anticipate macros to entirely subsume the current API?
I don't expect the Effect
encoding to change. Nonetheless, static metaprogramming has the potential to disrupt many common dart patterns. I would advise against using fpdart v2 in production yet.
That being said, the API as it is now works and hopefully won't need to change too much.
Static metaprogramming has the potential to disrupt many common dart patterns. I would advise against using fpdart v2 in production yet.
It's primarily for a personal project, so there's not really a "production".
I don't expect the
Effect
encoding to change ... That being said, the API as it is now works and hopefully won't need to change too much.
Ok, thanks! I guess I'll give it a go and report back with any feedback. Thanks again for fpdart!
Finally got some time to work on migrating some code to fpdart.
Feedback:
- I miss the default
tryPromise
overload. - ~~I haven't used
Cause
much, but I'm wanting it now.~~ I found cause right after I wrote my own impl π. ~~Could it get its own file? (moved comment)~~ - Is a
schema
package planned? Dart doesn't really have a "Zod". - Dart's lack of row polymorphism for records is annoying. I thought it'd be a good way to type context, but it intentionally doesn't work.
P.S.: I'll update this comment if I have anything else to add instead of notifying everyone.