我是C#中功能性思维方式的新手(嗯...不限于语言)。假设有一种方法:
T LoadRecord<T>(int id)
概念
1.验证
当给出无效输入时,我应该返回类似
Either<IEnumerable<ValidationError>, T>
的内容。2.执行
当调用可能抛出的DB/API/...时,我应该返回
Either<Exception, T>
。3.一些记录或无记录
因为条目可能存在或可能不存在,所以我返回
Option<T>
。最终签名
如果将以上所有内容结合起来,最终会得到以下结果:
Either<IEnumerable<ValidationError>, Either<Exception, Option<T>> LoadRecord<T>(int id)
我可以介绍以下类型:
Validation<T>
(〜: Either<IEnumerable<ValidationError>, T>
)Result<T>
(〜: Either<Exception, T>
)现在,我方法的签名如下所示:
Validation<Result<Option<T>>> LoadRecord<T>(int id)
用法
return LoadRecord<User>(1).Match(
vl => BadRequest(),
vr => vr.Match(
el => StatusCode(500),
er => er.Some(u => Ok(u))
.None(NotFound());
强制执行
try
{
var u = LoadRecord<User>(id);
if (u == null)
{
return NotFound();
}
return Ok(u);
}
catch (ValidationException)
{
return BadRequest();
}
catch (Exception)
{
return StatusCode(500);
}
问题
如您所见,方法的签名仍然很奇怪-乍一看,它给您“验证”,但是我真的在要求
T
。用法看起来也不是很漂亮,但确实比命令式的更为简洁。这是正确的方法吗?有没有办法提高代码的签名/可读性?
最佳答案
(bool failed, T result)
。局限性在于,没有方法负责正确的链接-按照Haskell的fmap
编码-您的容器(更好的说,但理论上不太正确-您的Functor
和Monad
很少见)。 Either<IEnumerable<ValidationError>, Either<Exception, Option<T>> LoadRecord<T>(int id)
-不。观察您的签名:和TFail
一样,您会得到IEnumerable<ValidationError>
或Exception
;因此,从技术上讲,您需要在那里使用Exception | IEnumerable<ValidationError>
类型,以为在C#
中实现需要花很多的工夫。 Option<T>
部分也是如此,它表示成功的结果:您不需要返回IOption
;即使存在,您的实现也会负责解包成功的结果并吐出T
或通过TFail
机制报告Either
错误。结论:是的,容器很棒,但要避免无尽的嵌套:有时间包装东西,有时间拆开它们:感受一下。 Rx
是一个很好的例子:它询问您onError: Action<Exception>
lambda,捕获异常时将调用该lambda。因此,您可以让客户自定义所需的行为,而不是一次对其进行硬编码。不仅如此,因为您的特定示例是特定的,所以IOption
是一种处理错误的方法,因此在您的特殊情况下-,但不是一般规则-不需要onError
:只需吞下一个异常并返回Nothing
即可。 关于C#FP : Validation and execution with error handling functional way - space for improvement?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/51865761/