-
How do you work with different nested monads within an public static Eff<Unit> doWork =>
...
from opt in getContent // gets Option<A>
from str in opt.ToEff() // unpacks option and fails if its None
...
static Eff<Option<string>> getContent =>... It does what I want, but I assume maybe there's an easier built-in way to do the same in one line? (Btw, I found mentions about language-ext v5, is there any list of upcoming changes?) |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 7 replies
-
The way that works for all nested monads is this: var mox = SuccessEff(Some(100));
var moy = SuccessEff(Some(200));
var mr = from ox in mox // Extract the Some(100)
from oy in moy // Extract the Some(200)
let rs = from x in ox // Extract the 100
from y in oy // Extract the 200
select x + y // Repackage as Some(300)
select rs; // Repackage as Eff(Some(300)) Because each LINQ block can only work with one monad at a time, you must extract the inner monads and the use them in a LINQ sub-block.
The transformer generated-code system can't quite handle The critical thing is to implement public static class EffOptionExt
{
public static Eff<Option<B>> BindT<A, B>(
this Eff<Option<A>> mma,
Func<A, Eff<Option<B>>> f) =>
from mmb in mma.Map(ma => ma.Map(f))
from mb in mmb.Match(
Some: identity,
None: () => SuccessEff(Option<B>.None))
select mb;
...
} It's worth breaking it down so you understand what's happening: from mmb in mma.Map(ma => ma.Map(f)) This line extracts (as from mb in mmb.Match(
Some: identity,
None: () => SuccessEff(Option<B>.None)) So, the next line matches on the select mb; That allows the next line to pack up the We could also write that like so: public static Eff<Option<B>> BindT<A, B>(
this Eff<Option<A>> mma,
Func<A, Eff<Option<B>>> f) =>
mma.Map(ma => ma.Map(f))
.Bind(mmb =>
mmb.Match(
Some: identity,
None: () => SuccessEff(Option<B>.None))); Which is a little more efficient, but maybe not as easy to read. Once you have // Alternative BindT that allows you to return an Option<B> rather
// than Eff<Option<B>>
public static Eff<Option<B>> BindT<A, B>(
this Eff<Option<A>> mma,
Func<A, Option<B>> f) =>
mma.BindT(a => SuccessEff(f(a)));
public static Eff<Option<B>> MapT<A, B>(
this Eff<Option<A>> mma,
Func<A, B> f) =>
mma.BindT(a => Some(f(a)));
public static Eff<Option<A>> FlattenT<A>(
this Eff<Option<Eff<Option<A>>>> mma) =>
mma.BindT(identity);
public static Eff<Option<B>> Select<A, B>(
this Eff<Option<A>> mma,
Func<A, B> f) =>
mma.BindT(a => Some(f(a)));
public static Eff<Option<B>> SelectMany<A, B>(
this Eff<Option<A>> mma,
Func<A, Eff<Option<B>>> f) =>
mma.BindT(f);
public static Eff<Option<C>> SelectMany<A, B, C>(
this Eff<Option<A>> mma,
Func<A, Eff<Option<B>>> bind,
Func<A, B, C> project) =>
mma.BindT(x => bind(x).MapT(y => project(x, y))); That will give you the shortcutting behaviour that you're expecting. But, it begs the question: why nest Essentially what I am saying is that the Calling parseInt("123").ToEff(Error.New("expected integer value")) Calling
As you've seen nested monads are still a little awkward to work with in C#. Part of the future roadmap will be to support transformer stacks like Haskell. Essentially you create new monadic types that are nested compositions of existing monads. This is still a little ways off. I will be making a big announcement about the future roadmap in the next few days. But, as people on social media like to say: there are big things coming! :) |
Beta Was this translation helpful? Give feedback.
-
I've stuck with a very similar question. For a given code: var result = from _ in RegisterAsync("[email protected]", "password")
from user in ByEmailAsync("[email protected]")
select GenerateFor(user);
var finalResult = await result; I'm getting an error at the line "select GenerateFor(user)": I.e. it can't unpack Right from Either, whereas I expect it to do due to short-circuiting when if Left occurs it just returns the value as Left to result. var result = await
_registerUser.RegisterAsync(request.Email, request.Password)
.BindAsync(_ => _fetchUser.ByEmailAsync(request.Email))
.BindAsync(user => _generateToken.GenerateFor(user));
```
No errors in that way.
Excuse me for not opening a separate discussion, I think the context matters.
Also, don't mind the code flow, I know it's a bit messy. Just ignore it, it will have a better data model. |
Beta Was this translation helpful? Give feedback.
The way that works for all nested monads is this:
Because each LINQ block can only work with one monad at a time, you must extract the inner monads and the use them in a LINQ sub-block.