container/adapters 设计
设计目标
container 包定义能力而不依赖任何具体类型。总得有人为仓库自己的类型提供字典,而这部分代码既要看到能力记录,也要看到每个具体包。container/adapters 就是这样的位置:一个依赖于它所适配的一切的叶子包,从而其他包都不必如此。
数学背景
适配器是具体类型表示了容器模型中抽象对象的证据(见 container 设计)。对类型 V,它通过 length 和 get 提供指称 ,并在可能时通过 tabulate 提供它的一个截面:
对视图而言,指称由形状为 的底层矩阵 计算得到:
视图没有 tabulate:构造必须创建一个底层矩阵,而结果将不再是调用者所持有的任何对象的视图。视图的可变编辑会写穿到 ,这正是视图的用途。
设计决策
独立的叶子包
问题。 如果 container 包含这些适配器,它就会依赖 immut、mutable 和 backends/default,而每个只需要能力记录的外部库都会引入所有具体类型。
决定。 记录和算法留在 container 中,它只依赖 error。适配器位于 container/adapters,它依赖 container、immut、mutable 和 backends/default。仓库中没有包依赖这些适配器。
理由。 这样依赖就从具体指向一般。外部库只需针对 container 发布自己的字典,参见集成指南。
每个类型与能力一个工厂函数
每个工厂函数的名称形如 <type>_<capability>_ops,并返回一个新记录。带有自由类型参数的泛型函数不能作为值存储,因此用工厂函数来提供“对每个 T,@immut.Matrix[T] 的读取字典”。工厂函数对 T 没有约束,因为读取、构造和编辑从不检查元素。
编辑模型遵循所有权
不可变类型及其包装类型获得持久化编辑;可变类型、视图和可变包装类型获得可变编辑。为可变矩阵提供持久化编辑,每次 set 都需要完整复制,这会把 的开销藏在一个看似 的调用后面;为不可变矩阵提供可变编辑则不可能不破坏其值语义。
先校验再委托
具体类型在索引非法时中止。每个适配器先检查索引或形状并返回错误值,因此即使它们调用的方法会中止,这些字典仍满足 container 的无 panic 契约。
正确性与不变量
- 每个读取适配器在合法索引上满足 ,在其他索引上返回
IndexOutOfBounds;构造器满足上述tabulate/get定律。 - 构造器按行主序对每个元素恰好调用一次初始化函数。
- 持久化编辑从不修改其参数:
@immut的更新是对持久化向量的路径复制。 - 视图的可变编辑只修改底层矩阵,不影响其他任何东西。
- 每个工厂函数都在 内运行;字典调用的开销与底层方法相同(
@mutable读取为 ,@immut为 )。
被否决的方案
- 在每个具体包内部提供适配器。 这样
immut和mutable就会依赖实验性的container层,使稳定的具体 API 继承其不稳定性。 - 为外部类型提供适配器。 本仓库只为自己维护的类型提供适配器;外部库自行负责其类型的适配器。
边界
该包不新增能力,也不新增算法,只为现有类型提供证据。它不为视图提供构造字典,不为可变类型提供持久化编辑,也不为本仓库之外的类型提供适配器。已撤下的 OpenBLAS 后端的适配器与该后端一起保存在 contrib/openblas_backend 中,不属于本包。