错误处理

计算 M 表达式的结果将生成以下结果之一:

  • 生成单个值。

  • 引发错误指示计算表达式的过程不能生成值 。 错误包含单个记录值,可用于提供有关导致不完整计算的原因的其他信息。

可从表达式中引发错误,也可从其中处理错误。

引发错误

引发错误的语法如下所示:

error-raising-expression:
      errorexpression

文本值可用作错误值的简写形式。 例如:

error "Hello, world" // error with message "Hello, world"

完整的错误值是记录,可以使用 Error.Record 函数进行构造:

error Error.Record("FileNotFound", "File my.txt not found",
     "my.txt")

上述表达式等效于:

error [ 
    Reason = "FileNotFound", 
    Message = "File my.txt not found", 
    Detail = "my.txt" 
]

引发错误将导致当前表达式计算停止,并将展开表达式计算堆栈,直到发生以下情况之一:

  • 已达到记录字段、节成员或 let 变量(统称为“条目”)。 该条目被标记为有错误,该错误值将与该条目一起保存,然后传播。 对该条目的任何后续访问都将导致引发相同的错误。 记录、节或 let 表达式的其他条目不一定会受到影响(除非它们访问之前标记为有错误的条目)。

  • 已达到顶级表达式。 在这种情况下,计算顶级表达式的结果是一个错误而不是一个值。

  • 已达到 try 表达式。 在这种情况下,将捕获错误并以值的形式返回。

处理错误

error-handling-expression(在非正式的情况下,也称为“try 表达式”)用于处理错误:

error-handling-expression:
      tryprotected-expression error-handleropt
protected-expression:
      表达式
error-handler:
      otherwise-clause
      catch-clause
otherwise-clause:

      otherwisedefault-expression
default-expression:
      expression
catch-clause:
      catchcatch-function
catch-function:
      (parameter-nameopt)=>function-body

在不使用 error-handler 计算 error-handling-expression 时,存在以下情况:

  • 如果计算 protected-expression 没有导致错误并生成值 x,则由 error-handling-expression 生成的值为以下形式的记录:
    [ HasErrors = false, Value = x ]
  • 如果计算 protected-expression 引发错误值 e,则 error-handling-expression 的结果为以下形式的记录:
    [ HasErrors = true, Error = e ]

在使用 error-handler 计算 error-handling-expression 时,存在以下情况:

  • 必须在 error-handler 之前计算 protected-expression。

  • 当且仅当计算 protected-expression 引发错误时,才必须计算 error-handler。

  • 如果计算 protected-expression 引发错误,则由 error-handling-expression 生成的值为计算 error-handler 的结果。

  • 传播在计算 error-handler 期间引发的错误。

  • 当要计算的 error-handler 是 catch-clause 时,将调用 catch-function。 如果该函数接受参数,则将错误值作为其值传递。

下面的示例演示了在没有引发错误的情况下的 error-handling-expression :

let
    x = try "A"
in
    if x[HasError] then x[Error] else x[Value] 
// "A"

下面的示例演示了引发错误,然后对其进行处理:

let
    x = try error "A" 
in
    if x[HasError] then x[Error] else x[Value] 
// [ Reason = "Expression.Error", Message = "A", Detail = null ]

通过将 catch-clause 与接受参数的 catch-function 结合使用,可以使用更少的语法重写前面的示例:

let
    x = try error "A" catch (e) => e
in
    x
// [ Reason = "Expression.Error", Message = "A", Detail = null ]

otherwise-clause 可用于将 try 表达式处理的错误替换为替代值:

try error "A" otherwise 1 
// 1

具有零参数 catch-function 的 catch-clause 实际上是 otherwise-clause 的一个较长的替代语法:

try error "A" catch () => 1 
// 1

如果 error-handler 也引发错误,那么整个 try 表达式也会引发错误:

try error "A" otherwise error "B" 
// error with message "B"
try error "A" catch () => error "B" 
// error with message "B"
try error "A" catch (e) => error "B" 
// error with message "B"

记录和 let 初始值设定项中的错误

下面的示例显示了一个记录初始值设定项,其中字段 A 引发错误,并且由两个其他字段 BC 访问。 字段 B 不处理 A 引发的错误,但是 C 会处理此错误。 最终字段 D 不访问 A,因此不受 A 中的错误影响。

[ 
    A = error "A", 
    B = A + 1,
    C = let x =
            try A in
                if not x[HasError] then x[Value]
                else x[Error], 
    D = 1 + 1 
]

计算以上表达式的结果是:

[ 
    A = // error with message "A" 
    B = // error with message "A" 
    C = "A", 
    D = 2 
]

M 中的错误处理应在接近错误原因的位置执行,以处理延迟字段初始化和延迟闭包计算的影响。 下面的示例演示了使用 try 表达式处理错误时的失败尝试:

let
    f = (x) => [ a = error "bad", b = x ],
    g = try f(42) otherwise 123
in 
    g[a]  // error "bad"

在此示例中,定义 g 用于处理调用 f 时引发的错误。 但是,此错误由仅在需要时(即从 f 返回记录并通过 try 表达式传递后)才运行的字段初始值设定项引发。

未实现的错误

当开发表达式时,作者可能希望省略表达式某些部分的实现,但可能仍希望能够执行表达式。 处理这种情况的一种方法是引发未实现部件的错误。 例如:

(x, y) =>
     if x > y then
         x - y
     else
         error Error.Record("Expression.Error", 
            "Not Implemented")

省略号符号 (...) 可用作 error 的快捷方式。

not-implemented-expression:
      ...

例如,下面的示例等效于前面的示例:

(x, y) => if x > y then x - y else ...