vkhorikov/CSharpFunctionalExtensions

[Question] Is it better to return 'Result' or 'Maybe' from methods? -- .FirstOrDefault() -- What is the recommended return type?

jeffward01 opened this issue · 5 comments

Hello!

The title basically says it all.

Let's say I have a method like this which uses EFCore and queries the DB:

// This method takes a 'where expression' as a parameter, then returns the first result in the result set. 
// If there are no results, null is naturally returned (thank you microsoft)
// Take note that it is nullable
public Student? GetFirstStudent(IQueryable<Student> queryable) 
{

  // dbContext removed for the example...
  return queryable.FirstOrDefault();

}

In the example above, and based on the examples in the README.md I would think that the return type should be Result<Student>

So if the 'Student' is null, then the Result will be failed.

BUT

Isn't that the same purpose and functionality as Maybe<T>?


Questions

1.) When is it appropiate / recommended to return Result<T>

2.) When is it appropiate / recommended to return Maybet<T>

Thanks all

Edit:

I noticed that in the Bind method it says this:

Use case: Transforming from one Maybe into another Maybe (like Maybe.Map but it transforms the Maybe instead of the inner value)
Note: the delegate (ex MakeAppleSauce) passed to Maybe.Bind() is only executed if the Maybe has an inner value

Now I have (2) examples:

This returns a Result<TEntity, Error> type:

var result =   this.Queryable.AsMaybe()
            .ToResult(DomainErrors.NullOrEmpty.DbSetQueryable)
            .Tap(_ => _.Apply(specification))
            .Tap(_ => _.WithId(entity.Id))
            .Bind(
                q => q.FirstMaybe()
                    .ToResult(DomainErrors.NotFound.BySpecificationAndKey))
            .Ensure(_ => _.IsActive(), DomainErrors.EntityState.EntityNotActive)
            .Tap(this.AttachEntity)
            .Tap(_ => _.MarkDeactivate());

^^ The above is expected to return result because the last method is Tap()
This is the description for Tap()

Executes the given action if the calling result is a success. Returns the calling result.

Second example:

This also returns a Result<TEntity, Error> type.

var thisIsAlsoResult = this.Queryable.AsMaybe()
            .ToResult(DomainErrors.NullOrEmpty.DbSetQueryable)
            .Ensure(_ => id.IsNotEmpty(), DomainErrors.NullOrEmpty.ProvidedKey)
            .Tap(_ => _.Apply(whereExpression))
            .Tap(_ => _.WithId(id))
            .Bind(
                _ => _.FirstOrDefault()
                    .AsMaybe()
                    .ToResult(DomainErrors.NotFound.ByWhereExpressionAndKey));

Based on the documentation in the README.md , I would expect the second example to return a Maybe<TEntity> instead.


Am I not understanding something?

If someone explains the use-case of Result<T> versus Maybe<t> I can add it to the documentation.

Thanks!

I found this similar issue that speaks Result<T> vs Maybe<T> #14

The recommended solution said by @vkhorikov is:

Result<Maybe<DataObject>> is exactly what I recommend you use, and that is what I use myself in similar situations.

The guideline here is that if the outcome of the operation is stable, meaning that it depends only on the presence of the data in the external system, use Maybe. If the outcome may vary and depends on some secondary attributes, such as network connectivity, use Result. Use Result<Maybe> if you expect both of these at the same time.

Another way to look at it: if you expect some operation to fail due to reasons that are not related to the callee itself, use Result to state that information in the method signature.


So with that in mind, should my example method be something like:

// This method takes a 'where expression' as a parameter, then returns the first result in the result set. 
// If there are no results, null is naturally returned (thank you microsoft)
// Take note that it is nullable
public Result<Maybe<Student>> GetFirstStudent(IQueryable<Student> queryable) 
{

  // dbContext removed for the example...
  return queryable.FirstOrDefault();

}

I just want to confirm / clarify my thought process.

Is this the suggested approach for this scenario?

Thanks

Well, in your particular situation Maybe<Student> would be more appropriate. Result<Maybe<Student>> is for when for e.g you are requesting a student from an external API and that API may or may not have that student, and also it may or may not fail due to let's say connectivity issues and you know how to process that connectivity issue.

Another example is input validation. For example, you are validating a student email address when registering a student, and that address may be empty (which is allowed) or it may be not empty but invalid (which is not allowed):

private Result<Maybe<EmailAddress>> ValidateEmailAddress(string emailAddress)
{
// ...
}

This is very helpful, thanks for being so helpful, the community here is great. So in summary:

  • Result == If it can fail or have a success.
    • Fail as in some issue arises where the task completes with an error
  • Maybe == When a value can be null or not null

Thanks so much! It takes a bit of time to get your head wrapped around the concepts! I elaborated on that a bit here in this discussion: #477 (reply in thread)

Thanks!

Fail as in some issue arises where the task completes with an error

May fail and also you know how to handle this failure. Otherwise, there's no point in wrapping it in a Result. Check out this article: https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/