C# FP:使用错误处理功能方式进行验证和执行 - space 进行改进?
C# FP: Validation and execution with error handling functional way - space for improvement?
我不熟悉 C# 中的函数式思维方式(好吧...不限于语言)。假设有方法:
T LoadRecord<T>(int id)
概念
1。验证
当给出无效输入时,我应该return类似Either<IEnumerable<ValidationError>, T>
。
2。执行
当调用可能抛出的 DB/API/... 时,我应该 return Either<Exception, T>
.
3。一些或none记录
因为条目可能存在也可能不存在我正在 returning 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);
}
问题
如您所见,方法的签名仍然很奇怪 - 乍一看它给了您 "validation",但我真的要求 T
。用法也不是很漂亮,但确实比命令式更简洁。
这是正确的做法吗?有没有办法改进 signature/readability 的代码?
- 嗯,是的。根据所涉及的复杂性,现代(我猜是 C# 7)元组可能就足够了:
(bool failed, T result)
。限制是没有负责正确链接的方法 - fmap
ing 在 Haskell 方面 - 你的容器(更好地说,但理论上不完全正确 - 你的 Functor
s 和,不太常见,你的 Monad
s).
Either<IEnumerable<ValidationError>, Either<Exception, Option<T>> LoadRecord<T>(int id)
- 不。观察您的签名:至于 TFail
,您要么得到 IEnumerable<ValidationError>
要么 Exception
;所以,从技术上讲,你需要一个 Exception | IEnumerable<ValidationError>
类型,认为在 C#
中实现需要相当多的样板代码。同样适用于 Option<T>
部分,它代表一个成功的结果:你不需要 IOption
被 returned;即使它在那里,您的 实现 也负责解包成功的结果并输出 T
,或者通过 Either
报告 TFail
错误机制。结论:是的,容器很棒,但要避免无休止的嵌套:有时间包装东西,也有时间打开它们:感受这一刻。
- 最后但并非最不重要的一点:异常处理应该委托给容器本身。
Rx
是一个很好的例子:它要求您提供 onError: Action<Exception>
lambda,它在捕获异常时被调用。因此,您可以让您的客户自定义所需的行为,而不是一次硬编码。不仅如此,由于您的特定示例是特定的,IOption
是一种处理错误的方法,因此在您的特定情况下 - 但不是一般规则 - 否 onError
需要:只需吞下一个异常和 return Nothing
.
我不熟悉 C# 中的函数式思维方式(好吧...不限于语言)。假设有方法:
T LoadRecord<T>(int id)
概念
1。验证
当给出无效输入时,我应该return类似Either<IEnumerable<ValidationError>, T>
。
2。执行
当调用可能抛出的 DB/API/... 时,我应该 return Either<Exception, T>
.
3。一些或none记录
因为条目可能存在也可能不存在我正在 returning 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);
}
问题
如您所见,方法的签名仍然很奇怪 - 乍一看它给了您 "validation",但我真的要求 T
。用法也不是很漂亮,但确实比命令式更简洁。
这是正确的做法吗?有没有办法改进 signature/readability 的代码?
- 嗯,是的。根据所涉及的复杂性,现代(我猜是 C# 7)元组可能就足够了:
(bool failed, T result)
。限制是没有负责正确链接的方法 -fmap
ing 在 Haskell 方面 - 你的容器(更好地说,但理论上不完全正确 - 你的Functor
s 和,不太常见,你的Monad
s). Either<IEnumerable<ValidationError>, Either<Exception, Option<T>> LoadRecord<T>(int id)
- 不。观察您的签名:至于TFail
,您要么得到IEnumerable<ValidationError>
要么Exception
;所以,从技术上讲,你需要一个Exception | IEnumerable<ValidationError>
类型,认为在C#
中实现需要相当多的样板代码。同样适用于Option<T>
部分,它代表一个成功的结果:你不需要IOption
被 returned;即使它在那里,您的 实现 也负责解包成功的结果并输出T
,或者通过Either
报告TFail
错误机制。结论:是的,容器很棒,但要避免无休止的嵌套:有时间包装东西,也有时间打开它们:感受这一刻。- 最后但并非最不重要的一点:异常处理应该委托给容器本身。
Rx
是一个很好的例子:它要求您提供onError: Action<Exception>
lambda,它在捕获异常时被调用。因此,您可以让您的客户自定义所需的行为,而不是一次硬编码。不仅如此,由于您的特定示例是特定的,IOption
是一种处理错误的方法,因此在您的特定情况下 - 但不是一般规则 - 否onError
需要:只需吞下一个异常和 returnNothing
.