This is the fifth post in my ongoing series covering the abstractions in Sasa. Previous posts:
- Sasa.Parsing - type-safe, extensible lexing and parsing framework
- Sasa.Dynamics - type-safe polytypic/reflective programming
- Sasa.Func - Type-Safe Delegate Combinators
- Sasa.Option - Handling Optional Values
Sasa.Result<T> encapsulating the results of a computation, either it's return value, or the error it generated, if any. For instance, you could spawn a thread to perform some calculation and deposit the result into an instance of Result<T>. Sasa.Result<T> is available in the core Sasa.dll.
Result<T> implements all the usual equality tests on itself, and on T, so you can perform direct comparisons to values.
Sasa.Result<T>.Value
The Sasa.Result<T>.Value property allows clients to obtain the value that was returned from the computation. If an error was instead generated, then this throws InvalidOperationException with the InnerException property set to the original exception:
Result<int> hasValue = 3; Result<int> noValue = new Result<int>(new ArgumentException("somearg")); Console.WriteLine(hasValue.Value); Console.WriteLine(noValue.Value); //InvalidOperationException // outputs: // 3 // InvalidOperationException
This property is also required by IValue<T>, which Result<T> implements.
Sasa.Result<T>.HasValue
The Sasa.Result<T>.HasValue property allows clients to check whether the result is a legitimate value, or if it's an error result. If HasValue returns true, then clients can safely access the Value property. If it's false, then doing so will throw InvalidOperationException:
Result<int> hasValue = 3; Result<int> noValue = new Result<int>(new ArgumentException("somearg")); Console.WriteLine(hasValue.HasValue); Console.WriteLine(noValue.HasValue); // outputs: // true // false
This property is also required by the IOptional<T>, which Option<T> implements.
Sasa.Result<T>.Error
The Sasa.Result<T>.Error property allows clients to access the exception that resulted from the computation, with the full stack trace intact:
void EnsureValue<T>(Result<T> result) { if (!result.HasValue) Environment.FailFast("Unexpected result!", result.Error); }
This property is also required by IResult<T>, which Result<T> implements.
Sasa.Result<T>.TryGetValue
TryGetValue permits accessing the encapsulated Value without throwing an exception if the result was an error:
Result<int> someInt = 3; int x; if (someInt.TryGetValue(out x)) Console.WriteLine(x); else Console.WriteLine(someInt.Error); Result<int> noInt = new ArgumentException("noInt"); if (noInt.TryGetValue(out x)) Console.WriteLine(x); else Console.WriteLine(noInt.Error); // output is: // 3 // System.ArgumentException: ...
This method is required by the IVolatile<T> interface, which Result<T> implements.
Sasa.Result<T>.| operator
The C# null coalescing operator, ??, is hard-coded to only apply to reference types or System.Nullable<T>. However, the short-circuiting logical-or operator can be overridden, and Result<T> does so to provide result-coalescing:
Result<int> someInt = 3; Result<int> noInt = new ArgumentException("noInt"); Console.WriteLine(someInt || 99); Console.WriteLine(noInt || 99); // output is: // 3 // 99
Sasa.Result<T>.Select
The Sasa.Result<T>.Select is the first in the series of methods used in the LINQ query pattern:
Result<int> someInt = 3; Result<int> noInt = new ArgumentException("noInt"); Console.WriteLine(someInt.Select(x => x > 0)); Console.WriteLine(noInt.Select(x => x > 0)); // output is: // true // Result<ArgumentException>
There is also an implementation of Select for the more abstract IResult<T> interface, but because Result<T> is a struct this would incur too much unnecessary boxing, so we override the Select and SelectMany extension methods with instance methods.
Sasa.Result<T>.Where
Sasa.Result<T>.Where method implements the LINQ where clause for query patterns on Result<T>:
Result<int> someInt = 3; Result<int> noInt = Result.Fail<int>(new NotSupportedException("noInt")); Console.WriteLine(someInt.Where(x => x > 0)); Console.WriteLine(someInt.Where(x => x < 0)); Console.WriteLine(noInt.Where(x => x == 0)); // outputs: // true // Result<ArgumentException> // Result<NotSupportedException>
Sasa.Result<T>.SelectMany
The most essential LINQ operator, the Sasa.Result<T>.SelectMany overloads implement chaining for result values:
var val = from y in 2.ToResult() from j in 7.ToResult() from x in (y + 2 + j).ToResult() select x; Console.WriteLine(val); var noval = from y in 2.ToResult() from j in Result.Fail<int>(new ArgumentException("j")) from x in (y + 2 + j).ToResult() select x; Console.WriteLine(noval); // output: // 11 // Result<ArgumentException>
There is also an implementation of SelectMany for the more abstract IResult<T> interface, but because Result<T> is a struct this would incur too much unnecessary boxing, so we override the Select and SelectMany extension methods with instance methods.
Sasa.Result.Try
The Sasa.Result.Try static extension methods allow clients to execute some code within an exception handling block, which then returns the appropriate result for you, ie. either a value or an error:
Result<int> someInt = Result.Try(() => 3); Result<int> noInt = Result.Try<int>(() => throw new Exception()); Console.WriteLine(someInt); Console.WriteLine(noInt); // output is: // 3 // Result<Exception>
Sasa.Result.Fail
The static Sasa.Result.Fail method creates a result failure of the appropriate type with the given exception:
Result<int> fail = Result.Fail<int>(new ArgumentException("fail")); Console.WriteLine(fail); // outputs: // Result<ArgumentException>
Sasa.Result.Success
The static Sasa.Result.Success method creates a successful result value of the given type:
Result<int> x = Result.Success(99); Console.WriteLine(x); // outputs: // 99
Sasa.Result.ToResult
The Sasa.Result.ToResult extension method overloads provide some simple conversions from values into results:
Result<int> someInt = 3.ToResult(); Result<int> optInt = 3.ToOption().ToResult(); Result<IEnumerable<int>> intList = new int[] { 3, 99, 66 }.ToResult();
The last overload over IEnumerable is useful because such sequences are often lazily evaluated, which means they may have hidden errors that you will only incur while iterating. This overload then turns a lazy sequence of possible unsafe errors into a lazy sequence of exception-safe result types.
Comments