Zoran Horvat 提出使用Either
类型来避免空指针检查并且在操作执行时不要忘记处理问题。 Either
在函数式编程中很常见。
为了阐述其用法,Zoran展示了一个类似于这样的例子:
void Main()
{
var result = Operation();
var str = result
.MapLeft(failure => $"An error has ocurred {failure}")
.Reduce(resource => resource.Data);
Console.WriteLine(str);
}
Either<Failed, Resource> Operation()
{
return new Right<Failed, Resource>(new Resource("Success"));
}
class Failed { }
class NotFound : Failed { }
class Resource
{
public string Data { get; }
public Resource(string data)
{
this.Data = data;
}
}
public abstract class Either<TLeft, TRight>
{
public abstract Either<TNewLeft, TRight>
MapLeft<TNewLeft>(Func<TLeft, TNewLeft> mapping);
public abstract Either<TLeft, TNewRight>
MapRight<TNewRight>(Func<TRight, TNewRight> mapping);
public abstract TLeft Reduce(Func<TRight, TLeft> mapping);
}
public class Left<TLeft, TRight> : Either<TLeft, TRight>
{
TLeft Value { get; }
public Left(TLeft value)
{
this.Value = value;
}
public override Either<TNewLeft, TRight> MapLeft<TNewLeft>(
Func<TLeft, TNewLeft> mapping) =>
new Left<TNewLeft, TRight>(mapping(this.Value));
public override Either<TLeft, TNewRight> MapRight<TNewRight>(
Func<TRight, TNewRight> mapping) =>
new Left<TLeft, TNewRight>(this.Value);
public override TLeft Reduce(Func<TRight, TLeft> mapping) =>
this.Value;
}
public class Right<TLeft, TRight> : Either<TLeft, TRight>
{
TRight Value { get; }
public Right(TRight value)
{
this.Value = value;
}
public override Either<TNewLeft, TRight> MapLeft<TNewLeft>(
Func<TLeft, TNewLeft> mapping) =>
new Right<TNewLeft, TRight>(this.Value);
public override Either<TLeft, TNewRight> MapRight<TNewRight>(
Func<TRight, TNewRight> mapping) =>
new Right<TLeft, TNewRight>(mapping(this.Value));
public override TLeft Reduce(Func<TRight, TLeft> mapping) =>
mapping(this.Value);
}
如您所见,操作
返回Either<Failture, Resource>
,稍后可以使用它来形成单个值,而不会忘记处理操作失败的情况。 请注意,所有故障都源自Failure
类,如果有多个故障,则可能出现问题。
这种方法的问题在于消耗该值可能会很困难。
我将用一个简单的程序展示复杂性:
void Main()
{
var result = Evaluate();
Console.WriteLine(result);
}
int Evaluate()
{
var result = Op1() + Op2();
return result;
}
int Op1()
{
Throw.ExceptionRandomly("Op1 failed");
return 1;
}
int Op2()
{
Throw.ExceptionRandomly("Op2 failed");
return 2;
}
class Throw
{
static Random random = new Random();
public static void ExceptionRandomly(string message)
{
if (random.Next(0, 3) == 0)
{
throw new InvalidOperationException(message);
}
}
}
请注意,此示例根本不使用Either
类型,但作者告诉我可以这样做。确切地说,我想将上面的示例转换为使用
Either
。换句话说,我想将我的代码转换为使用Either并正确使用它。
注:有一个包含最终错误信息的Failure类和一个包含int值的Success类是有意义的。
额外说明:如果
Failure
能够包含可能在评估期间发生的所有问题的摘要,那将非常有趣。这种行为能够向调用方提供更多关于失败的信息,不仅是第一个失败的操作,还有随后的失败。我考虑编译器在语义分析期间的情况。我不希望该阶段在检测到第一个错误时退出,而是要收集所有问题以获得更好的体验。
Either
类似乎非常复杂,看起来你只是想要一个简单的错误管理,可以通过异常处理或具有属性以供检查错误的简单响应对象来实现。 - asaf92Either
使忘记处理失败在语法上不可能,因为它强制你将失败“映射”到有效结果(如字符串)。因此,Either
所需的类的实现非常复杂(很难理解)。很抱歉我无法提供更多信息。我已经寻找了更多示例和见解,但恐怕只有少数人能回答这个问题。【祈求好运】 - SuperJMN