Skip to content

Result monad

Leonid Gordo edited this page Oct 16, 2017 · 4 revisions

The monad allows to specify the result of some calculation.

Basic features

To create a result just use one of the methods of the helper class named Result. For example, to create a value that is the result of successful calculation, call Result.Success method.

var result = Result.Success(5);

The same way, to create a value that is the result of failed calculation, call Result.Fail method.

var result = Result.Fail<int>();

To obtain the value of the result, access Value property of the result instance.

var resultValue = result.Value;

If result object is in failed state, then call to Value property results to ApplicationException signalling that "Cannot obtain value for not succeed result". To check either result is succeed or not you can look for IsSucceed or IsFailed properties.

var isSucceed = result.IsSucceed;
var isFailed = result.IsFailed;

Another option is to use the fluent syntax that enables flexible way to operate with results. If you want to conditionally call to subsequent calculations only in case if result is still succeed, then you can chain functions using Success extension method.

var result = CalculationMethodA()
	.Success(v => CalculationMethodB(v))
	.Success(v => CalculationMethodC(v));

Each calculation method should return result value, that will be unwrapped and passed to the next method only if the result is still succeed.

The same way, you can make conditional call to method in case if result is already failed.

var result = CalculationMethodA()
	.Failed(() => CalculationMethodB())
	.Failed(() => CalculationMethodC());

Or mix both methods:

var result = CalculationMethodA()
	.Success(v => CalculationMethodB(v))
	.Failed(() => CalculationMethodC());

In this scenario, the CalculationMethodB will be called only if CalculationMethodA is succeed and CalculationMethodC will be called if either of previous calls is failed.

You can compose the results of calculations and make subsequent calculations in case if all or any of results are succeed or failed.

var resultA = CalculationMethodA();
var resultB = CalculationMethodB();

var result = resultA.And(resultB)
	.Success(v => CalculationMethodC(v))
	.Failed(() => CalculationMethodD());

In this scenario, the CalculationMethodC will be called if both CalculationMethodA and CalculationMethodB are succeed. This method should receive the tuple of two results as the parameter. In case if you want to compose more than two results using subsequent calls to And method then the tuple will contain the number of composed results up to 8. More than 8 composed results are available using AggregateResult class. The CalculationMethodD will be called if any of the previous calls is failed.

var resultA = CalculationMethodA();
var resultB = CalculationMethodB();

var result = resultA.Or(resultB)
	.Success(v => CalculationMethodC(v))
	.Failed(() => CalculationMethodD());

In this scenario the CalculationMethodC will be called if any of the previous calls is succeed and vise versa the CalculationMethodD will be called if all of the previous calls are failed.

It is a little bit inconvenient to mix both approach to handle the errors: by using result values and by handling exceptions. If you like to merge both approaches into single one then you can convert some known exceptions into the result values. Say, you want to call some method and to consider some of known exceptions that can be thrown by that method as result values. To achieve such behavior call to Result.Eval static method.

var result = Result.Eval(SomeFunctionThatCanThrowExceptions,
						typeof(ExceptionTypeA),
						typeof(ExceptionTypeB));

This example code calls SomeFunctionThatCanThrowExceptions and returns the result that is failed if exception of one of the given types is thrown. If method will throw an exception of type that is not listed in arguments to Eval method than this exception will be provided to the caller code unhandled.

Result values can be composed using And and Or extension methods. The result of composition is wrapped into AggregateResult type. To access the individual result value you can use AggregateValue property that returns a tuple of result values.

var resultA = CalculationMethodA();
var resultB = CalculationMethodB();
var resultC = CalculationMethodC();
var resultD = CalculationMethodD();

var result = resultA
		.And(resultB)
		.And(resultC)
		.And(resultD);

resultA == result.AggregateValue.Item1
resultA == result.AggregateValue.Item2
resultC == result.AggregateValue.Item3
resultD == result.AggregateValue.Item4

In case if aggregate result is composed of more than 8 result values than composition is packed into AggregateCollectionResult. Both AggregateResult and AggregateCollectionResult can be iterated.

var resultA = CalculationMethodA();
var resultB = CalculationMethodB();
var resultC = CalculationMethodC();
var resultD = CalculationMethodD();

var result = resultA
		.And(resultB)
		.And(resultC)
		.And(resultD);

resultA == result.ElementAt(0)
resultA == result.ElementAt(1)
resultC == result.ElementAt(2)
resultD == result.ElementAt(3)

Result builder

Is some scenarios you want to have not just a value and a market showing the success of calculation, but some other value telling what exactly goes wrong if the result is in failed state. That the ResultBuilder type is for. The Result static type follows the builder pattern and provides two methods: For and And. Let's see how to make the result value that looks very similar to the simple result value in success state.

var result = Result.For(5).And<string>();

This code creates the result value which stores the success integer value equals to 5 and can store some string that describes what goes wrong. The same way we create the result that is failed.

var result = Result.For<int>().And("It was terribly incorrect input data!");

And this code creates the result value which stores string value linked to the error state and can store some integer success value. Please, note that you cannot create a result value with both success and fail values set. At every single moment the result can store the only one value - either success or fail ones.

Twist

Let's imagine a function returns a result value that stores the maybe value. If you intend to chain that function call with subsequent processing of internal maybe value, than you could face with the sort of inconvenience. For such scenarios here is the special function named Twist that can unwrap result value, then unwrap internal monadic value, and then pack them in reverse order.

var result = GiveMeAResultStream(); // returns Result<Maybe<Stream>>	
var maybe = result.Twist(); // Maybe<Result<Stream>>

Now you can handle Maybe value as usual, optionally accessing wrapped result value.

The same way it works in reverse order.

var maybe = GetMeAMaybeString(); // returns Maybe<Result<Stream>>
var result = maybe.Twist(); // Result<Maybe<Stream>>

Now you can use all the power of result values while optionally accessing wrapped maybe value.

Basic types extensions

It's very common scenario when you need to convert string value into one of the base type values. Among them are integers, booleans, date and times, unique identifiers, etc. Basic class library gives you a bunch of methods with output parameters like TryParse but it requires more code lines and increases code complexity. This library offers you to use extension methods that return a result value, thus moving you to the area where you can easily handle with results using common way described above.

Target type Extension method Result value
Boolean ToBool() Result<bool>
DateTime ToDateTime()
ToDateTime(DateTimeStyles, IFormatProvider)
ToDateTime(string, DateTimeStyles, IFormatProvider)
Result<DateTime>
Guid ToGuid()
ToGuid(string)
Result<Guid>
Int32 ToInt()
ToInt(NumberStyles, IFormatProvider)
Result<int>
UInt32 ToUInt()
ToUInt(NumberStyles, IFormatProvider)
Result<uint>
Int64 ToLong()
ToLong(NumberStyles, IFormatProvider)
Result<long>
UInt64 ToULong()
ToULong(NumberStyles, IFormatProvider)
Result<ulong>
Int16 ToShort()
ToShort(NumberStyles, IFormatProvider)
Result<short>
UInt16 ToUShort()
ToUShort(NumberStyles, IFormatProvider)
Result<ushort>

Throwing out of the output parameters

By the way, you can convert any functions with output parameters to new one without them, but returning the result value. Imagine, you have old-school function

bool GetSomeIntResult(SomeType parameter1, SomeType2 parameter2, out int intResult)

This function tries to return the its result via the output parameter and the status of the operation via the result value itself. Looks nasty! Just convert it into nice looking function:

Result<int> GetSomeIntResultBetterVersion(SomeType parameter1, SomeType2 parameter2)

It is very easy using Funcs.MakeResult static call.

var func = Funcs.MakeResult<SomeType, SomeType2, int>(GetSomeIntResult);

And just call it.

var result = func(value, value2);