-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Do Notation #26
Comments
Hi @prazedotid Yes, I agree that the Do Notation would be awesome. It is certainly in the package scope. It requires some work to be implemented properly, but it will come to fpdart as well if possible 🔜 |
Been playing with a do notation implementation using async / await functions. Reasonably simple, but it does have some trade-offs: typedef TaskEither<L, R> = Future<Either<L, R>> Function();
typedef _DoAdapter<E> = Future<A> Function<A>(TaskEither<E, A>);
_DoAdapter<L> _doAdapter<L>() => <A>(task) => task().then(either.fold(
(l) => throw either.UnwrapException(l),
(a) => a,
));
typedef DoFunction<L, A> = Future<A> Function(_DoAdapter<L> $);
// ignore: non_constant_identifier_names
TaskEither<L, A> Do<L, A>(DoFunction<L, A> f) {
final adapter = _doAdapter<L>();
return () => f(adapter).then(
(a) => either.right<L, A>(a),
onError: (e) => either.left<L, A>(e.value),
);
} Usage: final TaskEither<String, int> myTask = Do(($) async {
await $(left("fail")); // execution will stop here
final count = await $(right(123));
return count;
}); Because it is async / await, it doesn't prevent someone from using a normal |
@tim-smart this looks really interesting 💡 I've played around with it, and this is the result (using class _EitherThrow<L> {
final L value;
const _EitherThrow(this.value);
}
typedef _DoAdapter<E> = Future<A> Function<A>(TaskEither<E, A>);
_DoAdapter<L> _doAdapter<L>() => <A>(task) => task.run().then(
(either) => either.getOrElse((l) => throw _EitherThrow(l)),
);
typedef DoFunction<L, A> = Future<A> Function(_DoAdapter<L> $);
TaskEither<L, A> Do<L, A>(DoFunction<L, A> f) => TaskEither.tryCatch(
() => f(_doAdapter<L>()),
(error, _) => (error as _EitherThrow<L>).value,
);
Which allows the following: final myTask = Do<String, double>(($) async {
final value = await $(TaskEither.right(10));
print("I am doing this with $value");
await $<int>(TaskEither.left("fail"));
print("...but not doing this");
final count = await $(TaskEither.of(2.5));
return count;
}); I will work more on this, which looks promising. I want to understand the implications, the risks, and the advantages. Did you think already about a list of downsides or risks? Do you have a longer example that shows how this helps clean up code? |
The biggest downside is forgetting to use await when running a task only for its side effects (ignoring the result). It is easy to spot when actually using the return value. Smaller trade offs include:
It is actually probably more useful for monads like Option / Either. You can implement it in a similar fashion: typedef _DoAdapter = A Function<A>(Option<A>);
A _doAdapter<A>(Option<A> option) => option._fold(
() => throw None(),
(value) => value,
);
typedef DoFunction<A> = A Function(_DoAdapter $);
// ignore: non_constant_identifier_names
Option<A> Do<A>(A Function(_DoAdapter $) f) {
try {
return Some(f(_doAdapter));
} on None catch (_) {
return None();
}
} |
@tim-smart I put this together for I added 3 functions to
I tested how this looks in an example and indeed it looks neat: /// No do notation
String goShopping() => goToShoppingCenter()
.alt(goToLocalMarket)
.flatMap(
(market) => market.buyBanana().flatMap(
(banana) => market.buyApple().flatMap(
(apple) => market.buyPear().flatMap(
(pear) => Option.of('Shopping: $banana, $apple, $pear'),
),
),
),
)
.getOrElse(
() => 'I did not find 🍌 or 🍎 or 🍐, so I did not buy anything 🤷♂️',
);
/// With do notation
String goShoppingDo() => Option.DoInit<String>(
($) {
final market = $(goToShoppingCenter().alt(goToLocalMarket));
final banana = $(market.buyBanana());
final apple = $(market.buyApple());
final pear = $(market.buyPear());
return 'Shopping: $banana, $apple, $pear';
},
).getOrElse(
() => 'I did not find 🍌 or 🍎 or 🍐, so I did not buy anything 🤷♂️',
);
/// or
String goShoppingDoContinue() =>
goToShoppingCenter().alt(goToLocalMarket).Do<String>(
(market, $) {
final banana = $(market.buyBanana());
final apple = $(market.buyApple());
final pear = $(market.buyPear());
return 'Shopping: $banana, $apple, $pear';
},
).getOrElse(
() => 'I did not find 🍌 or 🍎 or 🍐, so I did not buy anything 🤷♂️',
); I am planning to do the same for Did you think about some downsides also for this Do notation with the |
I would prefer to see only one way of initializing do notation, then composing it with other operators: Option.of(1).flatMap((i) => Option.Do(($) {
final sum = $(Option.of(i + 1));
return sum;
}));
Option.of(1).call(Option.Do(($) {
final a = $(Option.of(2));
return a;
}));
Option.Do(($) {
final a = $(Option.of(2));
return a;
});
|
@tim-smart Agree. I left one /// Init
String goShoppingDo() => Option.Do(
($) {
final market = $(goToShoppingCenter().alt(goToLocalMarket));
final amount = $(market.buyAmount());
final banana = $(market.buyBanana());
final apple = $(market.buyApple());
final pear = $(market.buyPear());
return 'Shopping: $banana, $apple, $pear';
},
).getOrElse(
() => 'I did not find 🍌 or 🍎 or 🍐, so I did not buy anything 🤷♂️',
);
/// FlatMap
String goShoppingDoFlatMap() => goToShoppingCenter()
.alt(goToLocalMarket)
.flatMap(
(market) => Option.Do(($) {
final banana = $(market.buyBanana());
final apple = $(market.buyApple());
final pear = $(market.buyPear());
return 'Shopping: $banana, $apple, $pear';
}),
)
.getOrElse(
() => 'I did not find 🍌 or 🍎 or 🍐, so I did not buy anything 🤷♂️',
);
/// call (andThen)
final doThen = Option.of(10)(Option.Do(($) => $(Option.of("Some")))); I am going to work on |
@SandroMaglione Did you need any help with this? I'm using |
@tim-smart The Do notation is work in progress in this branch. My plan would be to release a beta version to test how it works and improve on it. |
Hello!
Thank you for your awesome package, really helped me implement most of the concepts I learned from fp-ts in Flutter.
But I do think it's missing something, and that is the Do Notation. I currently have a use case where I need to preserve values across multiple
TaskEither
s, and I think the Do Notation approach can solve this.Is there going to be a Do Notation implementation soon in
fpdart
? Or do you have an alternative approach to my case?Thank you!
The text was updated successfully, but these errors were encountered: