mutable 设计

设计目标

mutable 门面包为 luna-poly 面向执行的那一半提供单一导入,其背后的层让算法可以原地更新多项式,同时计算出与 immut 层完全相同的结果。这一层以性能为先,但其副作用必须停留在调用者看得见的地方:在名字表明会进行修改的方法中。

数学背景

可变多项式是一个值为多项式的变量;这些值与不可变层中的数学对象相同,采用相同的规范形式。原地操作是赋值 p←p∘qp \leftarrow p \circ q,该层的契约是

p.op_inplace(q)  leaves p equal to p_before op q,\texttt{p.op\_inplace(q)}\ \text{ leaves } p \text{ equal to } \texttt{p\_before op q},

其中 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 对称性

两层在名字、参数顺序和带检查变体的约定上保持一致。已记录的差异如下:

领域immutmutable
转换无每个类型都有 from_immut、to_immut
复制与重置值两者都不需要copy, clear (Copyable, Clearable, MutablePolynomial)
稠密更新用 from_coefficients 重建set_coefficient(无带检查形式)、add_inplace、mul_inplace、scale_inplace
项更新+, scaleadd_term_inplace, add_inplace, mul_inplace, scale_inplace
稀疏更新add_term(返回新值)set_coefficient, add_term_inplace, add_inplace, mul_inplace, scale_inplace
上下文二元运算add_checked, mul_checkedadd_inplace、mul_inplace(会中止);带检查形式经由 ops()
上下文绑定接受不可变的 term/sparse 多项式接受可变的 term/sparse 多项式
代换载荷immut ContextSubstitutionValue自有的 ContextSubstitutionValue,持有可变多项式
sparse copy 的约束—需要 Eq + AddMonoid 系数
ExponentVector, Variable, VariableContextcore 类型相同的 core 类型

正确性 / 不变量

  • 在每个容器中,每次公开调用之后都是规范形式。
  • 对每个操作,用 to_immut 转换后的可变结果等于对转换后的输入进行不可变操作的结果。
  • 没有可变容器与任何其他值共享可变存储。
  • 把容器作为其自身的参数传入(p.add_inplace(p)、p.mul_inplace(p))得到 2p2p 和 p2p^2。

被否决的替代方案

  • 没有不可变对应物的纯可变类型。 值语义是更安全的默认选择;变更是在每个调用点按需选择的优化。
  • 会修改操作数的运算符。 它们会使每个 a * b 都可能产生副作用。
  • 为可变层独立实现算法会使必须保持一致的代码量翻倍。

边界

  • 该门面包不添加任何自有的函数或类型。
  • 容器不是快照:把容器赋给第二个绑定会共享它;请使用 copy()。
  • 委托的操作要付出转换代价;这一层优化的是更新,而不是算法本身。
  • 不可变层不做的一切(除法、因式分解、Gröbner 基),这一层同样不做。