0

I have a class Hopefully<T> that contains either an error or a value. Whenever a method returns one of these I need to check for errors and either handle them right there, or propagate further up the call-chain:

class Hopefully<T> 
{ 
   MyError Error; 
   T SomeTValue; 
   // Implicit conversion should be defined here from Error and from T
}

public Hopefuly<int> SquareRoot(int x) 
{ 
   return x >= 0 ? sqrt(x) : new MyError(...); 
}

public Hopefull<string> PrettySquareRoot(int x)
{
   var r = SquareRoot(x);     // r is Hopefully<int>
   if (r.IsError)
     return r.Error;          // r.Error is MyError
   var v = r.SomeTValue;      // v is int
   return $"the root is {v}";
}

I find this too verbose, and would like to write like this:

public Hopefull<string> PrettySquareRoot(int x)
{
   var v = assume SquareRoot(x); 
   // v is int; if callee returned error it's escalated automatically before v is assigned
   return $"the root if {v}";
}

Here the new assume keyword would check the right-side, and if it's an error it would immediately return from the method with that error. If there is no error, it would extract SomeTValue and assign it to v.

So it's similar to await in appearance, and similar to exceptions in behavior.

Compared to exception this would have better performance on millions of errors (yes, I need every one of those errors). It's also more explicit - a programmer can silently ignore exceptions but cannot do that to Hopefully<T> - they have to unpack the value.

The nearest I have found so far is this [1] but this seems a lot, and there is no information on how to integrate this into Visual Studio. I would like to hear other ideas before I dive into it.

[1] Is there a way to implement custom language features in C#?

DenNukem
  • 8,014
  • 3
  • 40
  • 45
  • No, you can't. But if you search nuget.org for "functional", you'll find plenty of libraries that have already invented this wheel and achieve a reasonably good level of comprehensibility within the C# syntax. – madreflection Dec 17 '19 at 23:07
  • This sounds a lot like [Railroad Programming](https://fsharpforfunandprofit.com/rop/) – gunr2171 Dec 17 '19 at 23:07
  • Indeed it does, @gunr2171. Thanks for the reference. – DenNukem Dec 17 '19 at 23:29

1 Answers1

1

You cannot extend the C# syntax. I suggest implementing a Chain method in the class Hopefully<T>

public Hopefully<U> Chain<U>(Func<T, U> convert)
{
    if (Error != null) {
        return Error;
    }
    return convert(SomeTValue);
}

Then you can write

public Hopefully<string> PrettySquareRoot(int x)
{
    return SquareRoot(x).Chain(v => $"the root is {v}");
}

Note that the type parameter of Chain can be inferred by C#.


My test implementation. I'm using Expression-bodied members (C# programming guide):

public class MyError
{
    public MyError(string message) => Message = message;
    public string Message { get; }
}

public class Hopefully<T>
{
    public Hopefully(T value) => SomeTValue = value;
    public Hopefully(MyError error) => Error = error;

    public static implicit operator Hopefully<T>(T value) => new Hopefully<T>(value);
    public static implicit operator Hopefully<T>(MyError error) => new Hopefully<T>(error);

    public MyError Error { get; }
    public T SomeTValue { get; }

    public Hopefully<U> Chain<U>(Func<T, U> convert)
    {
        if (Error != null) {
            return Error;
        }
        return convert(SomeTValue);
    }

    public override string ToString() =>
        Error != null ? $"Error: {Error.Message}" : SomeTValue.ToString();
}

The SquareRoot method

public Hopefully<int> SquareRoot(int x)
{
    if (x >= 0) {
        return (int)Math.Sqrt(x);
    }
    return new MyError("The argument of SquareRoot must be greater than or equal to 0");
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Thanks for the very detailed response! I though I tried this in the past, but let me try again now. – DenNukem Dec 18 '19 at 01:38