mutable 设计
设计目标
mutable 门面包为 luna-poly 面向执行的那一半提供单一导入,其背后的层让算法可以原地更新多项式,同时计算出与 immut 层完全相同的结果。这一层以性能为先,但其副作用必须停留在调用者看得见的地方:在名字表明会进行修改的方法中。
数学背景
可变多项式是一个值为多项式的变量;这些值与不可变层中的数学对象相同,采用相同的规范形式。原地操作是赋值 ,该层的契约是
其中 p_before op q 由不可变算法计算。每个观察(次数、项、求值、相等)只依赖于当前值。
设计决策
与 immut 同名的门面包
该门面包以与 immut 门面包相同的名字重新导出 core、luna-generic 代数 trait 以及四种可变表示。在两层之间切换算法基本上只需更改导入;针对能力 trait 或操作记录编写的泛型代码在两层上都能运行。
变更须主动选择并显式命名
只有 set_coefficient、clear、add_term_inplace 和 *_inplace 方法会改变其接收者。运算符和其他所有方法都返回新值。即使对可变类型,x + y 也从不改变 x,因此算术表达式在两层中读起来是一样的。
每次返回之前都恢复规范形式
每个修改方法都让容器保持规范:修剪过的系数数组、有序且已合并的项数组、不含零的稀疏映射。这些不变量与 immut 中相同,因此 ==、degree、size 和各种形状具有相同的含义。
委托算法,特化更新
非平凡的算法(Karatsuba、复合、导数、幂、多元乘积以及全部上下文逻辑)只在 immut 中实现一次,通过转换来使用。可变包只直接实现能从原地存储中获益的部分:系数 setter、稠密原地加法、稀疏单项更新。一致性测试检查两层结果一致。
边界处的所有权
每次转换都会复制或重建存储,除非存储的值本身是不可变的(上下文单元),因此可变容器从不与不可变值或另一个容器共享存储:
from_immut和to_immut会复制(dense、term、sparse)或共享一个不可变值(context);copy()给出一个独立的容器;to_coefficients和to_terms等查询方法返回新数组。
与 immut 的 API 对称性
两层在名字、参数顺序和带检查变体的约定上保持一致。已记录的差异如下:
| 领域 | immut | mutable |
|---|---|---|
| 转换 | 无 | 每个类型都有 from_immut、to_immut |
| 复制与重置 | 值两者都不需要 | copy, clear (Copyable, Clearable, MutablePolynomial) |
| 稠密更新 | 用 from_coefficients 重建 | set_coefficient(无带检查形式)、add_inplace、mul_inplace、scale_inplace |
| 项更新 | +, scale | add_term_inplace, add_inplace, mul_inplace, scale_inplace |
| 稀疏更新 | add_term(返回新值) | set_coefficient, add_term_inplace, add_inplace, mul_inplace, scale_inplace |
| 上下文二元运算 | add_checked, mul_checked | add_inplace、mul_inplace(会中止);带检查形式经由 ops() |
| 上下文绑定 | 接受不可变的 term/sparse 多项式 | 接受可变的 term/sparse 多项式 |
| 代换载荷 | immut ContextSubstitutionValue | 自有的 ContextSubstitutionValue,持有可变多项式 |
sparse copy 的约束 | — | 需要 Eq + AddMonoid 系数 |
ExponentVector, Variable, VariableContext | core 类型 | 相同的 core 类型 |
正确性 / 不变量
- 在每个容器中,每次公开调用之后都是规范形式。
- 对每个操作,用
to_immut转换后的可变结果等于对转换后的输入进行不可变操作的结果。 - 没有可变容器与任何其他值共享可变存储。
- 把容器作为其自身的参数传入(
p.add_inplace(p)、p.mul_inplace(p))得到 和 。
被否决的替代方案
- 没有不可变对应物的纯可变类型。 值语义是更安全的默认选择;变更是在每个调用点按需选择的优化。
- 会修改操作数的运算符。 它们会使每个
a * b都可能产生副作用。 - 为可变层独立实现算法会使必须保持一致的代码量翻倍。
边界
- 该门面包不添加任何自有的函数或类型。
- 容器不是快照:把容器赋给第二个绑定会共享它;请使用
copy()。 - 委托的操作要付出转换代价;这一层优化的是更新,而不是算法本身。
- 不可变层不做的一切(除法、因式分解、Gröbner 基),这一层同样不做。