Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expressing typical map() function #140

Open
vermiculus opened this issue Aug 2, 2022 · 12 comments
Open

Expressing typical map() function #140

vermiculus opened this issue Aug 2, 2022 · 12 comments

Comments

@vermiculus
Copy link

My googling has come up dry, so I thought I'd ask here: is there any implementation of the typical map() you would find in other implementations of the result monad? I'm trying to get something closer to LanguageExt's implementation without the apparent expectation that it's being used in LINQ syntax. (Really, all of this is to try to emulate Rust's first-class support for the concept.)

Here's an extension method that gets at the syntax I'm looking for, but I can't shake the feeling that I'm working around something that's already available in FluentResults:

public static Result<TNew> Map<TNew, TOld>(this Result<TOld> @this, Func<TOld, Result<TNew>> func)
{
  if (@this.IsSuccess)
  {
    return func(@this.Value);
  }
  return new Result<TNew>()
    .WithSuccesses(@this.Successes)
    .WithErrors(@this.Errors);
}

used as

Result<Foo> GetFoo() { ... }
Result<Bar> GetBar(Foo foo) { ... }

Result<Bar> result = GetFoo().Map(x => GetBar(x));

I can understand if it's not FluentResult's primary intention to be used as a result monad, but it seems it's very close to having this capability assuming it doesn't have the capability already.

@ricksanchez
Copy link
Contributor

Hi, having a .Map doesn't make an object to be a monad, only a functor. I think the .Map can be a handy alias of .ToResult. @altmann what do you think?

Consider this code:

Result<int> ri = Result.Ok(1);
Result<string> rs1 = ri.ToResult(static i => i.ToString());
Result<string> rs2 = ri.Map(static i => i.ToString());

@vermiculus
Copy link
Author

Perhaps I'm not doing something correctly -- I don't think ToResult has the behavior I need.

image
image

Notice how the second ToResult returns a Result<Result<T>> instead of just a Result<T>.

However, with Map:

image
image

@ricksanchez
Copy link
Contributor

Ok, .Map functions normally map the value of the structure into a new value. A function called .Bind or .FlatMap, or .Then or .AndThen.

If you come from Haskell, .Map is like fmap and the others are like >>=. Rust equivalent is .and_then (Option type).

I would suggest to call it .FlatMap. Maybe it can implement IEnumerable and .SelectMany (losing its errors).

@vermiculus
Copy link
Author

vermiculus commented Aug 5, 2022

and_then is indeed what I'm talking about, but in the Result type 😄 though this implementation of Result<T> doesn't map cleanly to Rust's Result<T,E>, so perhaps you just mean that this implementation is closer to Option<T>.

Yeah, in retrospect, Map wasn't the best name for my extension method. I was copying typescript-monads' syntax just from familiarity with another project that uses it, but thanks for pointing out the discrepancy!

@altmann
Copy link
Owner

altmann commented Aug 5, 2022

Hi guys!

First: ToResult(...) is not what you want. See https://github.com/altmann/FluentResults#converting for details - ToResult() is for converting from Result<T> to Result or vice versa.

Second: This functional magic was already discussed in some issues in the past (#132, #108). Please read it. Summary: I don't want to introduce such a complexity in the FluentResults library.
If you need it then please implement it in your own project and please let us know in some months how was the dev experience.

@ricksanchez
Copy link
Contributor

I have already 2 projects using the .Bind method. Before this, we were treating the Result in similar way as golang treat errors, it was too pattern-y imperative handling of errors.

The .Bind method will bring OOP style, rather than structural/procedural. I wouldn't call it "functional magic". Because it is a technique of Category Theory, popular in FP, and yet not part of it. And we are going to express it in OOP (kind of smalltalk style, methods over statements and message passing).

I see @vermiculus can also benefit.

I can submit an implementation if you agree.

@altmann
Copy link
Owner

altmann commented Aug 11, 2022

How many methods do you think you will add? Can you write down the signatures please so that I get the big picture. Thanks

@ricksanchez
Copy link
Contributor

ricksanchez commented Aug 11, 2022

public Result<TNewValue> Bind<TNewValue>(Func<TValue, Result<TNewValue>> valueConverter)

The name is debatable, since there is the ToResult<TNewValue> method, maybe BindResult or ToFlatResult.

The idea is simple, be able to call functions that take a T with a Result<T>.

Additionally, if agreed, the BindAsync can be implemented for convenience.

Maybe an alias for ToResult<T> as Map<T> ?

@altmann
Copy link
Owner

altmann commented Aug 11, 2022

Please create a pr so that I get the idea. MVP style - let the ToResult(..) stable and don't create a Map alias for them.

@altmann
Copy link
Owner

altmann commented Sep 7, 2022

Sorry for the delay. I reviewed the pr and merged it into the master. I will wait for a pr of another issue - by the end of the week I will publish a new version of FluentResults

@altmann
Copy link
Owner

altmann commented Sep 11, 2022

New package is out https://www.nuget.org/packages/FluentResults/3.12.0

Thanks for your time inventstment.

@eshepelyuk
Copy link

Hi all

So, currently there is .Bind() and .Map() exists.
What is the difference between both ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants