error 教程
本教程演示如何处理受检线性代数调用的失败:识别出了什么问题,在合理时进行恢复,串联多个受检步骤,并用同一套词汇从你自己的辅助函数中报告失败。错误类型背后的道理见错误设计。
快速上手
moon add Luna-Flow/linear-algebra@0.5.0
///|
import {
"Luna-Flow/linear-algebra/error" @la_error,
"Luna-Flow/linear-algebra/mutable",
}
受检调用返回 Result[_, LinearAlgebraError]。对结果进行分支,并用谓词对失败分类:
///|
test "classify a failed inverse" {
let m = @mutable.Matrix::from_2d_array([[1.0, 2.0], [2.0, 4.0]])
let status = match m.inverse() {
Ok(_) => "ok"
Err(e) => if e.is_singular_matrix() { "singular" } else { e.message }
}
inspect(status, content="singular")
}
输出为 singular。
日常任务
针对不同的失败采取不同的恢复方式
///|
fn err_tut_inverse_report(m : @mutable.Matrix[Double]) -> String {
match m.inverse() {
Ok(inv) => "inverse has \{inv.row()} rows"
Err(e) =>
if e.is_non_square_matrix() {
"not square: use a least-squares method instead"
} else if e.is_singular_matrix() {
"singular: regularize the matrix"
} else {
e.message
}
}
}
///|
test "different recovery paths" {
let rect = @mutable.Matrix::from_2d_array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
inspect(
err_tut_inverse_report(rect),
content="not square: use a least-squares method instead",
)
let ok = @mutable.Matrix::from_2d_array([[2.0, 0.0], [0.0, 4.0]])
inspect(err_tut_inverse_report(ok), content="inverse has 2 rows")
}
串联受检步骤
只有当两个步骤都成功时才计算 。提前 return Err(e) 会把第一个失败原样传递出去:
///|
fn err_tut_trace_of_power(
a : @immut.Matrix[Int],
k : Int,
) -> Result[Int, @la_error.LinearAlgebraError] {
let p = match a.pow(k) {
Ok(p) => p
Err(e) => return Err(e)
}
p.trace()
}
///|
test "trace of a matrix power" {
let a = @immut.Matrix::from_2d_array([[1, 1], [1, 0]])
inspect(err_tut_trace_of_power(a, 5).unwrap(), content="11")
match err_tut_trace_of_power(a, -1) {
Err(e) => inspect(e.is_negative_exponent(), content="true")
Ok(_) => fail("negative powers are not defined")
}
}
Fibonacci 矩阵的 是 ,因此迹为 。
从自己的辅助函数中报告失败
使用构造函数返回共享 kind 的错误:
///|
fn err_tut_column_sum(
m : @mutable.Matrix[Int],
col : Int,
) -> Result[Int, @la_error.LinearAlgebraError] {
guard col >= 0 && col < m.col() else {
return Err(
@la_error.LinearAlgebraError::index_out_of_bounds(
"column \{col} is outside 0..<\{m.col()}",
),
)
}
let mut sum = 0
m.each_col(col, x => sum = sum + x)
Ok(sum)
}
///|
test "a custom checked helper" {
let m = @mutable.Matrix::from_2d_array([[1, 2], [3, 4]])
inspect(err_tut_column_sum(m, 1).unwrap(), content="6")
match err_tut_column_sum(m, 5) {
Err(e) => {
inspect(e.is_index_out_of_bounds(), content="true")
inspect(e.message, content="column 5 is outside 0..<2")
}
Ok(_) => fail("column 5 does not exist")
}
}
包装标量失败
当辅助函数混合了受检标量运算和矩阵运算时,请包装 ArithmeticError,使该辅助函数只有单一的错误类型:
///|
fn err_tut_rms(
m : @mutable.Matrix[Double],
) -> Result[Double, @la_error.LinearAlgebraError] {
let n = (m.row() * m.col()).to_double()
let mut sum = 0.0
m.each(x => sum = sum + x * x)
let ctx = @lf_arith.ArithmeticContext::new(53)
match @la_arithmetic.CheckedDiv::checked_div(sum, n, ctx) {
Ok(mean_sq) => Ok(mean_sq.sqrt())
Err(e) => Err(@la_error.LinearAlgebraError::arithmetic_failure(e))
}
}
///|
test "wrapping a scalar error" {
let m = @mutable.Matrix::from_2d_array([[3.0, 4.0]])
inspect(err_tut_rms(m).unwrap(), content="3.5355339059327378")
let empty : @mutable.Matrix[Double] = @mutable.Matrix::new(0, 0, 0.0)
match err_tut_rms(empty) {
Err(e) => inspect(e.is_arithmetic_failure(), content="true")
Ok(_) => fail("0 / 0 must fail")
}
}
进一步了解
非受检形式。 每个受检方法都有一个 unchecked_* 对应版本,它中止而不是返回 Err(unchecked_inverse 返回 Option)。只有在你的代码已经确立了前置条件之后才使用它,例如在遍历你按方阵构造的矩阵的内层循环中。定律是:只要 x 在定义域内,就有 checked(x) == Ok(unchecked(x))。
仍会中止的方法。 本版本中并非每个部分运算都有受检形式。from_2d_array 等构造函数在行长不一致时中止,运算符 +、- 和 * 在形状不匹配时中止,eigen 在输入非对称时中止。immut 和 mutable 的 API 页面逐一标注了这些情况;当输入来自程序外部时,请先校验。
来自 container 层的错误。 container 的字典和算法返回同一种错误类型,因此先转换存储再计算的流水线无需在错误类型之间转换。
常见陷阱
- 对
message进行匹配。 消息是诊断信息,可能在版本之间变化;请依据谓词或kind分支。 - 直接打印错误。
LinearAlgebraError没有实现Show或Debug;请打印e.message。 - 比较整个错误。
==也会比较消息。若只关心类别,请比较a.kind == b.kind。 - 以为保留的 kind 会出现。 本版本不会产生
NonConvergence、RaggedRows和InvalidLength。
后续步骤
- error API:所有构造函数和谓词。
- 错误设计:受检/非受检定律。
- mutable 教程和 immut 教程:受检矩阵运算本身。