I\'m trying to create a type similar to Rust\'s Result or Haskell\'s Either and I\'ve got this far:
public struct Result
The reason for the warning is explained in the section The issue with T? of Try out Nullable Reference Types. Long story short, if you use T? you have to specify whether the type is a class or struct. You may end up creating two types for each case.
The deeper problem is that using one type to implement Result and hold both Success and Error values brings back the same problems Result was supposed to fix, and a few more.
Result (and Either) in F#
The starting point should be F#'s Result type and discriminated unions. After all, this already works on .NET.
A Result type in F# is :
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
The types themselves only carry what they need.
DUs in F# allow exhaustive pattern matching without requiring nulls :
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
Emulating this in C# 8
Unfortunately, C# 8 doesn't have DUs yet, they're scheduled for C# 9. In C# 8 we can emulate this, but we lose exhaustive matching :
#nullable enable
public interface IResult{}
struct Success : IResult
{
public TResult Value {get;}
public Success(TResult value)=>Value=value;
public void Deconstruct(out TResult value)=>value=Value;
}
struct Error : IResult
{
public TError ErrorValue {get;}
public Error(TError error)=>ErrorValue=error;
public void Deconstruct(out TError error)=>error=ErrorValue;
}
And use it :
IResult Sqrt(IResult input)
{
return input switch {
Error e => e,
Success (var v) when v<0 => new Error("Negative"),
Success (var v) => new Success(Math.Sqrt(v)),
_ => throw new ArgumentException()
};
}
Without exhaustive pattern matching, we have to add that default clause to avoid compiler warnings.
I'm still looking for a way to get exhaustive matching without introducing dead values, even if they are just an Option.
Option/Maybe
Creating an Option class by the way that uses exhaustive matching is simpler :
readonly struct Option
{
public readonly T Value {get;}
public readonly bool IsSome {get;}
public readonly bool IsNone =>!IsSome;
public Option(T value)=>(Value,IsSome)=(value,true);
public void Deconstruct(out T value,out bool isSome)=>(value,isSome)=(Value,IsSome);
}
//Convenience methods, similar to F#'s Option module
static class Option
{
public static Option Some(T value)=>new Option(value);
public static Option None()=>default;
}
Which can be used with :
string cateGory = someValue switch { Option (_ ,false) =>"No Category",
Option (var v,true) => v.Name
};