This library attempts to use the ROP design by using LINQ. In most functional
programming languages, ROP is implemented by making use of the Result or
Either monad. In C#, one of the two methods for having language integrated
monad support is via LINQ.
This library implements the Result monad as a value type implementing the
Select and SelectMany methods. This allows multiple from and let clauses,
the select clause, and the select ... into clause. Most other clauses tend
to be collection specific and are not currently implemented.
Two static methods exist for creating a result, Ok and Error. This will
create the result, set the success status, and set the value to the value
passed in.
The main focus of this project is the Result<TOk, TError> type. This type
can hold one of two values, either a TOk to indicate that the process was
successful, or a TError to indicate the process failed. Direct access to
the value is not allowed; instead, callbacks are used to process the value
to prevent attempting to access a value which is invalid for the given context.
There are five methods which allow access to the internal value.
The Either method ends the ROP flow by converting the final result to a
single value. Two callbacks are required, one of which will be called with
the TOk value if the result indicates success, and one of which will be
called with the TError value if the result indicates failure. Both of
these callbacks are expected to return the same type. This method returns
the value of whichever callback was called.
There are two overloads of the Do method. They are used to perform actions
based on the value of the result.
The first overload is used to perform inline operations during the ROP
flow process. Assuming the result indicates success, the callback will
be called with the current TOk value. If the result indicates failure,
the callback is ignored. The original result is returned to allow continued
use in the ROP flow.
The second overload ends the ROP flow by performing an action on the value
of the result. Two callbacks are required. okAction is called if the
result was successful, and errorAction is called if the result failed.
The Select method is used to change the TOk value to a new value. This
results in a new Result with the new TOk value if the result indicates
success. A callback is required which will convert the TOk to a new TOk.
The SelectError method is similar to the Select method, except that it
converts the TError value.
The Collapse method is available for Result only in the specific case that
TOk and TError are the same type. The Collapse method returns the value.
It is the same as calling the Either method as Either(_ => _, _ => _)
Typical use of LinqToResult is to create individual steps of an application as
methods returning the Result type if the method has a failure case, and
returning a regular value if it does not have a failure case.
from value1 in MethodReturningResult1()
from value2 in MethodReturningResult2(value1)
let value3 = NoFailMethod(value1, value2)
select (value2, value3)Each from clause will assign the TOk value from the result if the result
indicates success, and will continue executing the rest of the query. In the
case the result indicates failure, the result is immediately returned without
executing the rest of the query. In this way, it is similar to exception
handling, but without having to allocate an exception object and without
creating a stack trace. For expected errors, this has both a performance
benefit as well as requiring that errors cases are handled without having
to handle error cases at every invocation.
Given the proliferation of the async/await pattern in C#, a many methods
which would return a result will also be asynchronous. In order to make
asynchronouos results easier to work with, most operations defined for
Result<TOk,TError> have been defined as extensions for
Task<Result<TOk,TError>. Specifically, SelectMany is defined on
Result<TOk,TError> accepting both Result<TOk,TError> and
Task<Result<TOk,TError>>, and Task<Result<TOk,TError>> is extended
to support accepting both Result<TOk,TError> and
Task<Result<TOk,TError>>.
Given how LINQ queries make use of the Select method, it is not possible
to make use of Task<T> values within a LINQ query where T is not
Result<TOk,TError>. To allow such operations, Task<T> has been
extended to support AsResult which will convert a Task<T> into
a Task<Result<T,TError>.