container API

Luna-Flow/linear-algebra/container 描述泛型代码如何在不了解存储方式的情况下观察、构建和编辑线性容器。一项能力就是一个操作字典:针对某个容器类型 V 或 M 和某个元素类型 T 的函数记录。五个泛型算法(映射、转换和转置)就是基于这些字典编写的。仓库自身类型的现成字典位于 container/adapters。

源码:src/container/vector_ops.mbt、src/container/matrix_ops.mbt、src/container/generic_algorithms.mbt。该模型见 container 设计;集成指南展示了外部库如何发布字典。

导入

///|
import {
  "Luna-Flow/linear-algebra/container",
  "Luna-Flow/linear-algebra/container/adapters" @container_adapters,
  "Luna-Flow/linear-algebra/error" @la_error,
}

所有字典共享的约定

字典中的每个函数都是受检的:它返回 Result,并且对任何参数都不得中止。

  • 在形状范围之外的索引处读取或编辑,会返回 kind 为 IndexOutOfBounds 的错误。
  • 以负长度或负维度进行构建,会返回 kind 为 NegativeDimension 的错误,并且不会调用初始化函数。
  • 退化形状 0×n0 \times n 和 n×0n \times 0 是合法的,必须被精确保留;它们与 0×00 \times 0 不同。
  • 矩阵坐标为 (row, col),从零开始。构建函数以 (row, col) 调用初始化函数。

记录字段是公开且可读的。将字段作为函数调用时,需要用括号包住字段访问:(ops.get)(m, 0, 1)。

向量字典

VectorReadOps

VectorReadOps[V, T] 用于观察元素类型为 T 的类向量容器 V。

pub struct VectorReadOps[V, T] {
  length : (V) -> Int
  get : (V, Int) -> Result[T, @error.LinearAlgebraError]
}

length(v) 返回元素个数 n≥0n \ge 0;get(v, i) 在 0≤i<n0 \le i < n 时返回第 ii 个元素,否则返回 IndexOutOfBounds。

VectorReadOps::new

VectorReadOps::new(length, get) 由两个函数构建读取字典。

pub fn[V, T] VectorReadOps::new((V) -> Int, (V, Int) -> Result[T, @error.LinearAlgebraError]) -> Self[V, T]

VectorBuildOps

VectorBuildOps[V, T] 由索引函数构造类型为 V 的向量。

pub struct VectorBuildOps[V, T] {
  tabulate : (Int, (Int) -> T) -> Result[V, @error.LinearAlgebraError]
}

tabulate(n, f) 返回向量 (f(0),…,f(n−1))(f(0), \dots, f(n-1)),当 n<0n < 0 时返回 NegativeDimension。

VectorBuildOps::new

VectorBuildOps::new(tabulate) 构建一个构建字典。

pub fn[V, T] VectorBuildOps::new((Int, (Int) -> T) -> Result[V, @error.LinearAlgebraError]) -> Self[V, T]

VectorPersistentEditOps

VectorPersistentEditOps[V, T] 替换一个元素并返回新值。

pub struct VectorPersistentEditOps[V, T] {
  set : (V, Int, T) -> Result[V, @error.LinearAlgebraError]
}

set(v, i, x) 返回一个除索引 ii 处为 x 外其余都与 v 相等的向量。即使实现在不同版本之间共享存储,参数 v 也必须保持不变。

VectorPersistentEditOps::new

VectorPersistentEditOps::new(set) 构建一个持久化编辑字典。

pub fn[V, T] VectorPersistentEditOps::new((V, Int, T) -> Result[V, @error.LinearAlgebraError]) -> Self[V, T]

VectorMutableEditOps

VectorMutableEditOps[V, T] 原地替换一个元素。

pub struct VectorMutableEditOps[V, T] {
  set : (V, Int, T) -> Result[Unit, @error.LinearAlgebraError]
}

set(v, i, x) 将 x 写入 v 的索引 ii 处。出错时不会写入任何内容。

VectorMutableEditOps::new

VectorMutableEditOps::new(set) 构建一个可变编辑字典。

pub fn[V, T] VectorMutableEditOps::new((V, Int, T) -> Result[Unit, @error.LinearAlgebraError]) -> Self[V, T]

矩阵字典

MatrixReadOps

MatrixReadOps[M, T] 用于观察类矩阵容器。

pub struct MatrixReadOps[M, T] {
  shape : (M) -> (Int, Int)
  get : (M, Int, Int) -> Result[T, @error.LinearAlgebraError]
}

shape(m) 返回 (rows, cols);get(m, r, c) 返回元素 (r,c)(r, c) 或 IndexOutOfBounds。

MatrixReadOps::new

MatrixReadOps::new(shape, get) 构建一个矩阵读取字典。

pub fn[M, T] MatrixReadOps::new((M) -> (Int, Int), (M, Int, Int) -> Result[T, @error.LinearAlgebraError]) -> Self[M, T]

MatrixBuildOps

MatrixBuildOps[M, T] 由坐标函数构造矩阵。

pub struct MatrixBuildOps[M, T] {
  tabulate : (Int, Int, (Int, Int) -> T) -> Result[M, @error.LinearAlgebraError]
}

tabulate(r, c, f) 返回元素为 f(i,j)f(i, j) 的矩阵,当 r<0r < 0 或 c<0c < 0 时返回 NegativeDimension。

MatrixBuildOps::new

MatrixBuildOps::new(tabulate) 构建一个矩阵构建字典。

pub fn[M, T] MatrixBuildOps::new((Int, Int, (Int, Int) -> T) -> Result[M, @error.LinearAlgebraError]) -> Self[M, T]

MatrixPersistentEditOps

MatrixPersistentEditOps[M, T] 替换一个元素并返回新矩阵,参数保持不变。

pub struct MatrixPersistentEditOps[M, T] {
  set : (M, Int, Int, T) -> Result[M, @error.LinearAlgebraError]
}

MatrixPersistentEditOps::new

MatrixPersistentEditOps::new(set) 构建一个持久化编辑字典。

pub fn[M, T] MatrixPersistentEditOps::new((M, Int, Int, T) -> Result[M, @error.LinearAlgebraError]) -> Self[M, T]

MatrixMutableEditOps

MatrixMutableEditOps[M, T] 原地替换一个元素。

pub struct MatrixMutableEditOps[M, T] {
  set : (M, Int, Int, T) -> Result[Unit, @error.LinearAlgebraError]
}

MatrixMutableEditOps::new

MatrixMutableEditOps::new(set) 构建一个可变编辑字典。

pub fn[M, T] MatrixMutableEditOps::new((M, Int, Int, T) -> Result[Unit, @error.LinearAlgebraError]) -> Self[M, T]

为普通 Array[Int] 定义的字典,通过其字段使用:

///|
fn cont_api_array_read() -> @container.VectorReadOps[Array[Int], Int] {
  @container.VectorReadOps::new(xs => xs.length(), (xs, i) => {
    guard i >= 0 && i < xs.length() else {
      return Err(
        @la_error.LinearAlgebraError::index_out_of_bounds("index \{i}"),
      )
    }
    Ok(xs[i])
  })
}

///|
test "a hand-written read dictionary" {
  let ops = cont_api_array_read()
  inspect((ops.length)([4, 5, 6]), content="3")
  inspect((ops.get)([4, 5, 6], 1).unwrap(), content="5")
  match (ops.get)([4, 5, 6], 3) {
    Err(e) => inspect(e.is_index_out_of_bounds(), content="true")
    Ok(_) => fail("index 3 is out of bounds")
  }
}

泛型算法

所有算法都先按行主序将整个源读入临时缓冲区,然后才调用目标的 tabulate。读取错误会在构建任何内容之前返回,因此永远不会观察到部分构建的目标。对于 nn 个元素,每个算法执行 O(n)O(n) 次字典调用,并使用 O(n)O(n) 的额外内存。

vector_map

vector_map(source, source_ops, target_ops, f) 构建一个目标类型的向量,其第 ii 个元素为 f(source[i])。

pub fn[V1, V2, A, B] vector_map(V1, VectorReadOps[V1, A], VectorBuildOps[V2, B], (A) -> B) -> Result[V2, @error.LinearAlgebraError]

如果 source_ops.length 报告负长度,它返回 NegativeDimension;否则返回第一个读取错误;最后返回 tabulate 的结果。f 对每个元素恰好按索引顺序调用一次。

vector_convert

vector_convert(source, source_ops, target_ops) 将向量复制到元素类型相同的另一种表示中。

pub fn[V1, V2, T] vector_convert(V1, VectorReadOps[V1, T], VectorBuildOps[V2, T]) -> Result[V2, @error.LinearAlgebraError]

它就是以恒等函数调用的 vector_map。

matrix_map

matrix_map(source, source_ops, target_ops, f) 构建一个形状相同的矩阵,其元素 (i,j)(i, j) 为 f(source[i][j])。

pub fn[M1, M2, A, B] matrix_map(M1, MatrixReadOps[M1, A], MatrixBuildOps[M2, B], (A) -> B) -> Result[M2, @error.LinearAlgebraError]

matrix_convert

matrix_convert(source, source_ops, target_ops) 将矩阵复制到元素类型和形状都相同的另一种表示中。

pub fn[M1, M2, T] matrix_convert(M1, MatrixReadOps[M1, T], MatrixBuildOps[M2, T]) -> Result[M2, @error.LinearAlgebraError]

matrix_transpose

matrix_transpose(source, source_ops, target_ops) 在目标表示中构建 source 的转置:对于形状为 (r,c)(r, c) 的源,结果形状为 (c,r)(c, r),元素 (i,j)(i, j) 等于源中的元素 (j,i)(j, i)。

pub fn[M1, M2, T] matrix_transpose(M1, MatrixReadOps[M1, T], MatrixBuildOps[M2, T]) -> Result[M2, @error.LinearAlgebraError]

与 @algebra.TransposeMatrix::transpose 不同,目标类型可以与源类型不同。

结合仓库适配器使用三个矩阵算法:

///|
test "map, convert and transpose across representations" {
  let source = @mutable.Matrix::from_2d_array([[1, 2, 3], [4, 5, 6]])
  let read = @container_adapters.mutable_matrix_read_ops()
  let doubled : @immut.Matrix[Int] = @container.matrix_map(
    source,
    read,
    @container_adapters.immutable_matrix_build_ops(),
    x => x * 2,
  ).unwrap()
  inspect(doubled, content="|2, 4, 6|\n|8, 10, 12|")
  let as_text : @immut.Matrix[String] = @container.matrix_map(
    source,
    read,
    @container_adapters.immutable_matrix_build_ops(),
    x => "<\{x}>",
  ).unwrap()
  inspect(as_text[1][2], content="<6>")
  let t : @immut.Matrix[Int] = @container.matrix_transpose(
    source,
    read,
    @container_adapters.immutable_matrix_build_ops(),
  ).unwrap()
  inspect(t, content="|1, 4|\n|2, 5|\n|3, 6|")
  let copy : @mutable.Matrix[Int] = @container.matrix_convert(
    t,
    @container_adapters.immutable_matrix_read_ops(),
    @container_adapters.mutable_matrix_build_ops(),
  ).unwrap()
  debug_inspect(copy.shape(), content="(3, 2)")
}

向量算法的用法与之相同:

///|
test "vector map and convert" {
  let v = @immut.Vector::from_array([1, 2, 3])
  let squares : @mutable.Vector[Int] = @container.vector_map(
    v,
    @container_adapters.immutable_vector_read_ops(),
    @container_adapters.mutable_vector_build_ops(),
    x => x * x,
  ).unwrap()
  inspect(squares, content="|1, 4, 9|")
  let back : @immut.Vector[Int] = @container.vector_convert(
    squares,
    @container_adapters.mutable_vector_read_ops(),
    @container_adapters.immutable_vector_build_ops(),
  ).unwrap()
  inspect(back, content="|1, 4, 9|")
}