CSharpFunctionalExtensions icon indicating copy to clipboard operation
CSharpFunctionalExtensions copied to clipboard

API review

Open space-alien opened this issue 5 years ago • 6 comments

I wanted to review the Result API before moving ahead further with #148 and #151 - this gives a chance to plan any further refactoring in one place, so we have a better sense of the overall direction. Hopefully, this can serve as a starting point for some documentation too. For now, rather than fleshing this out with comprehensive descriptions, I am just going to add links to open issues and my ideas for improvements, highlighted in bold.

Updated 10 Mar 2020

Properties

Name Type Description
IsFailure bool Whether the result is a failure.
IsSuccess bool Whether the result is a success.
Error string or E The error for a failure result. Accessing this property throws an exception if the result is a success. Use Result<T, E> to supply custom error types.
Value T The value for a success result. Accessing this property throws an exception if the result is a failure.

Static result creation methods

Name Description
Combine() Combines several results into a single result. The returned result will be a failure if any of the input results are failures.
Failure() Creates a failure result.
FailureIf(bool) Creates a result whose success/failure reflects the supplied condition. Opposite of SuccessIf().
FirstFailureOrSuccess() Returns the first failure from the supplied results. Returns a new success result if there is no failure.
Success() Creates a success result.
SuccessIf(bool) Creates a result whose success/failure reflects the supplied condition. Opposite of FailureIf().
Try() Attempts to execute the supplied action. Returns a Result indicating whether the action executed successfully.

Instance methods

Name Description
ConvertFailure<K>() Throws if the result is a success. Else returns a new failure result of the given type. See #136 and #148.

Extension methods

Name Description
Bind() BindWithTransactionScope()
Combine()
Deconstruct()
Ensure(bool predicate) Returns a new failure result if the predicate is false. Otherwise returns the starting result.
Finally(Func) Passes the result to the given function (regardless of success/failure state) to yield a final output value.
Map()
MapWithTransactionScope()
Creates a new result from the return value of a given function. If the calling Result is a failure, a new failure result is returned instead.
Match()
OnFailure() See #148
OnFailureCompensate() See #148
OnSuccessTry() TODO
SelectMany()
Tap(Action action) Executes the given action if the calling result is a success. Returns the calling result.
TapIf(bool condition) If the condition is true, calls Tap().

Static configuration fields

Name Description
ErrorMessagesSeparator A string that is used to separate any error messages concatenated by Combine()
DefaultConfigureAwait Default value for the continueOnCapturedContext parameter of the ConfigureAwait() method, which is called on every awaited Task.

space-alien avatar Sep 08 '19 10:09 space-alien

Great overview. I like Result.SuccessIf(bool) better than Result.CreateSuccessIf(bool). And just as a reminder - we need to keep the old Ok etc under the Obsolete status.

I'm also thinking that maybe Result.Ok() should be left without the Obsolete attribute. Ok is too wide-spread and intuitive to get rid of it for the sake of consistency. I don't have a strong opinion here, though.

vkhorikov avatar Sep 09 '19 21:09 vkhorikov

echoing @vkhorikov very good proposal. my vote on static result creation methods is taking the first approach. but i think moving from struct to class and the loss of non-nullability is a major issue. isn't it one of the pillars of this library? struct also have the advantage of memory allocation, moving to classes might have impact on GC when dealing with lots of results.

golavr avatar Sep 09 '19 22:09 golavr

Regarding structs vs classes. Can interfaces help?

vkhorikov avatar Sep 10 '19 01:09 vkhorikov

Our teams use this library very heavily. Our vote would be

Result.Create(bool)         =>  Result.SuccessIf(bool)
Result.CreateFailure(bool)  =>  Result.FailIf(bool)
Result.Ok()                 =>  keep as it is and add Success()
Result.Fail()               =>  keep as it is

altmann avatar Sep 13 '19 13:09 altmann

I have pushed some commits and we now have:

Result.Create(bool) [Obsolete]         =>  Result.SuccessIf(bool)
Result.CreateFailure(bool) [Obsolete]  =>  Result.FailureIf(bool)
Result.Ok()                            =>  Result.Success()
Result.Fail() [Obsolete]               =>  Result.Failure()

The reasons for Failure() and not Fail() are consistency and better semantics. "Fail" is a verb and therefore carries incorrect connotations of having some ("failing") effect on the Result. Also, the failure/success flags are IsFailure and IsSuccess respectively. For this reason, Fail is marked Obsolete.

Ok() is not marked Obsolete for now, but we might reconsider this later.

space-alien avatar Sep 16 '19 16:09 space-alien

Updated initial post

space-alien avatar Sep 16 '19 16:09 space-alien