我将从在F#中创建和捕获异常转移到围绕Result<'T, 'TError>构建的东西上。我找到了this,它同意我最初的追求,即用一个受歧视的联盟来表示失败,但我遇到了一个问题,即我的Failure受歧视的联盟有很多不同的案例:
type TypedValue =
| Integer of int
| Long of int64
| …
type Failure =
| ArgumentOutOfRange of {| Argument : TypedValue; Minimum : TypedValue; Maximum : TypedValue |}
| BufferTooSmall of {| RequiredSize : int |}
| Exception of exn
| IndexOutOfRange of {| Index : int |}
| …我不希望有大量的类型专门用于错误处理。这种“类型化的值”的东西一点也不优雅,因为我要么必须创建冲突的名称(Byte与System.Byte),要么创建长名称以避免冲突(| UnsignedByte of byte)。
泛型是可能的,但是Failure<'T>中的'T代表什么呢?ArgumentOutOfRange不是唯一的区分联合,有些情况可能需要更多的类型参数,或者根本不需要类型参数。
发布于 2019-03-27 19:59:09
在你有一定需要处理的自定义类型的错误的情况下,或者在你有一些其他的逻辑来传播错误而不是由标准异常实现的错误的情况下,使用Result<'T, 'TError>是很有意义的(例如,如果你可以继续运行代码,尽管有一个错误)。然而,我不会用它作为异常的1:1替代-它只会使你的代码变得不必要的复杂和笨拙,而不会给你带来太多的好处。
为了回答您的问题,由于您是在区分的联合中镜像标准.NET异常,因此您可能只需要在Result类型中使用标准.NET异常,并使用Result<'T, exn>作为数据类型:
if arg < 10 then Error(ArgumentOutOfRangeException("arg", "Value is too small"))
else OK(arg - 1)关于ArgumentOutOfRange联合用例和TypedValue -使用像TypedValue这样的东西的原因通常是你需要对可能的值进行模式匹配,并对它们做一些事情。如果出现异常,您希望如何处理这些值?如果您只需要将它们报告给用户,那么您可以使用obj,它可以让您轻松地打印它们(获取数值并对其进行进一步计算并不是那么容易,但我认为您不需要这样做)。
type Failure =
| ArgumentOutOfRange of {| Argument : obj; Minimum : obj; Maximum : obj |}发布于 2019-03-27 20:50:44
另一种选择(我个人通常也是这么做的)是在Failure联盟中使用特定案例对特定于域的故障进行建模,然后创建一个通用的UnexpectedError案例,该案例将exn作为其数据并处理任何与域无关的故障。然后,当一个域中的错误出现在另一个域中时,您可以使用Result.mapError在它们之间进行转换。下面是一个我已经建模的真实领域的例子:
open System
// Top-level domain failures
type EntityValidationError =
| EntityIdMustBeGreaterThanZero of int64
| InvalidTenant of string
| UnexpectedException of exn
// Sub-domain specific failures
type AccountValidationError =
| AccountNumberMustBeTenDigits of string
| AccountNameIsRequired of string
| EntityValidationError of EntityValidationError // Sub-domain representaiton of top-level failures
| AccountValidationUnexpectedException of exn
// Sub-domain Entity
// The fields would probably be single-case unions rather than primitives
type Account =
{
Id: int64
AccountNumber: string
}
module EntityId =
let validate id =
if id > 0L
then Ok id
else Error (EntityIdMustBeGreaterThanZero id)
module AccountNumber =
let validate number =
if number |> String.length = 10 && number |> Seq.forall Char.IsDigit
then Ok number
else Error (AccountNumberMustBeTenDigits number)
module Account =
let create id number =
id
|> EntityId.validate
|> Result.mapError EntityValidationError // Convert to sub-domain error type
|> Result.bind (fun entityId ->
number
|> AccountNumber.validate
|> Result.map (fun accountNumber -> { Id = entityId; AccountNumber = accountNumber }))https://stackoverflow.com/questions/55369082
复制相似问题