fpdart icon indicating copy to clipboard operation
fpdart copied to clipboard

fpdart v2.0.0

Open SandroMaglione opened this issue 11 months ago β€’ 21 comments

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 includes Option and Either (since both extend IEffect)
  • No more jargon: easy to understand method names instead of fp jargon (e.g. succeed instead of pure)
  • 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 the effect library (typescript), which itself was inspired from ZIO.

The Effect class and methods in fpdart are based on effect 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

SandroMaglione avatar Mar 18 '24 06:03 SandroMaglione

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.

cavin-7span avatar Mar 19 '24 18:03 cavin-7span

Released the first pre-release: fpdart v2.0.0-dev.1

SandroMaglione avatar Mar 23 '24 08:03 SandroMaglione

Hello! Thanks for your mission. But I missed rightsEither(). for me it's good function for safety data load

airychkov avatar Mar 23 '24 22:03 airychkov

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), ),

airychkov avatar Mar 24 '24 20:03 airychkov

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.

ryanhanks-bestow avatar Mar 26 '24 17:03 ryanhanks-bestow

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.

utamori avatar Mar 27 '24 01:03 utamori

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 :)

hbock-42 avatar Mar 28 '24 03:03 hbock-42

New release: fpdart v2.0.0-dev.2:

  • Complete Option and Either API
  • Execute Effect using provide (with Null as dependency)
  • Fixed implementation of running Effect and catching Cause
  • Added interruption (Cause.Interrupted)
    • Deferred
    • Context
    • New methods (raceAll, race, delay, sleep, timeout)

SandroMaglione avatar Mar 28 '24 21:03 SandroMaglione

Error stack not so good. Only show main function call, not show when happen

airychkov avatar Mar 29 '24 22:03 airychkov

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

airychkov avatar Mar 31 '24 10:03 airychkov

New release: fpdart v2.0.0-dev.3:

  • Added Scope
  • const constructor for None

SandroMaglione avatar Apr 03 '24 21:04 SandroMaglione

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

ryanhanks-bestow avatar Apr 03 '24 23:04 ryanhanks-bestow

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

ryanhanks-bestow avatar Apr 03 '24 23:04 ryanhanks-bestow

@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 avatar Apr 04 '24 06:04 ryanhanks-bestow

@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?

SandroMaglione avatar Apr 04 '24 08:04 SandroMaglione

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.

lishaduck avatar Jul 24 '24 01:07 lishaduck

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.

SandroMaglione avatar Jul 24 '24 14:07 SandroMaglione

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?

lishaduck avatar Jul 25 '24 01:07 lishaduck

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.

SandroMaglione avatar Jul 31 '24 17:07 SandroMaglione

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!

lishaduck avatar Jul 31 '24 18:07 lishaduck

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.

lishaduck avatar Aug 10 '24 21:08 lishaduck