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的错误,并且不会调用初始化函数。 - 退化形状 和 是合法的,必须被精确保留;它们与 不同。
- 矩阵坐标为
(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) 返回元素个数 ;get(v, i) 在 时返回第 个元素,否则返回 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) 返回向量 ,当 时返回 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) 返回一个除索引 处为 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 的索引 处。出错时不会写入任何内容。
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) 返回元素 或 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) 返回元素为 的矩阵,当 或 时返回 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。读取错误会在构建任何内容之前返回,因此永远不会观察到部分构建的目标。对于 个元素,每个算法执行 次字典调用,并使用 的额外内存。
vector_map
vector_map(source, source_ops, target_ops, f) 构建一个目标类型的向量,其第 个元素为 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) 构建一个形状相同的矩阵,其元素 为 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 的转置:对于形状为 的源,结果形状为 ,元素 等于源中的元素 。
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|")
}