Chapter 9. Thinking about data functionally
覆盖mutation pitfalls、state/identity/change、immutability enforcement与functional data structures,以snapshot和structural sharing管理版本。
学习目标
- 能分析shared mutation的alias、ordering与concurrency风险,设计immutable snapshot transition
- 能区分value equality、entity identity、event identity与object reference,绘制state version chain
- 能设计constructor/copy/persistent structure边界,并用allocation/retention evidence评估成本
机制总览
Chapter 9. Thinking about data functionally:机制路径
- 1
为什么Data最难的不是存什么,而是谁能改
Mutable object把identity和current state放在同一个reference里;任何alias都可观察/触发变化,reasoning需要知道所有writers和order。Functional data view把state当value,change产生new versio…
- 2
The pitfalls of state mutation
Mutation引入temporal coupling:结果取决于“谁先改”。Concurrent writes需要locks/version checks;tests需复杂fixture;cache key或hash collection中的mutable key会破坏lookup。Encapsu…
- 3
Understanding state, identity…
Value由contents决定sameness;entity由stable domain id跨versions保持identity;event是已发生fact,有自己的id/order;object reference只说明runtime allocation相同。把这四者混淆会导致错误equa…
章级决策实验
Chapter 9. Thinking about data functionally:机制与证据
切换《Chapter 9. Thinking about data functionally》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么Data最难的不是存什么,而是谁能改
Mutable object把identity和current state放在同一个reference里;任何alias都可观察/触发变化,reasoning需要知道所有writers和order。Functional data view把state当value,change产生new versio…
可核验证据
以确定输入重复运行「为什么Data最难的不是存什么,而是谁能改」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
学完《Chapter 9. Thinking about data functionally》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 9. Thinking about data functionally:失效与核验
为什么Data最难的不是存什么,而是谁能改
典型失效
若把「为什么Data最难的不是存什么,而是谁能改」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。
核验证据
以确定输入重复运行「为什么Data最难的不是存什么,而是谁能改」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
The pitfalls of state mutation
典型失效
若把「The pitfalls of state mutation」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。
核验证据
以确定输入重复运行「The pitfalls of state mutation」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
Understanding state, identity…
典型失效
若把「Understanding state, identity…」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。
核验证据
以确定输入重复运行「Understanding state, identity…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
为什么Data最难的不是存什么,而是谁能改
Mutable object把identity和current state放在同一个reference里;任何alias都可观察/触发变化,reasoning需要知道所有writers和order。Functional data view把state当value,change产生new version,旧version不变。Identity仍存在,但由domain id连接versions,而不是把memory reference当全部语义。
先预测:readonly field是否让referenced object immutable;copy一个List是否deep immutable;record是否自动解决entity identity;persistent structure是否不占额外memory;local mutation是否永远不允许。答案都是否。
↡多个code paths持有同一mutable object reference,任一write改变其他observer可见state的关系。The pitfalls of state mutation
Mutation引入temporal coupling:结果取决于“谁先改”。Concurrent writes需要locks/version checks;tests需复杂fixture;cache key或hash collection中的mutable key会破坏lookup。Encapsulation能限制writers,但若getter暴露mutable collection,边界仍泄露。
public sealed class Order
{
private readonly List<Line> lines = new();
public IReadOnlyList<Line> Lines => lines.ToArray();
}Defensive copy只是一种边界,large data需persistent collections/read-only views和owner policy。目标不是复制一切,而是禁止未授权alias write。
切换alias write、copy、concurrent与snapshot
Understanding state, identity, and change
Value由contents决定sameness;entity由stable domain id跨versions保持identity;event是已发生fact,有自己的id/order;object reference只说明runtime allocation相同。把这四者混淆会导致错误equality、dedup、cache和concurrency policy。
Change可表示OldState + Command -> NewState/Events。Expected version保护concurrent update;event id保护duplicate。State transition尽量pure,persistence shell负责atomic compare-and-swap/append。
切换value、entity、event与reference
Enforcing immutability
第一版C#常用private readonly fields、getter-only properties、constructor/factory validation、no setters和copy methods。Collection用immutable/persistent type或defensive snapshot。Modern records/init可简化syntax,但with是shallow copy,referenced members仍可能mutable。
public sealed class AccountState
{
public decimal Balance { get; }
public long Version { get; }
public AccountState Credit(decimal amount) =>
new(Balance + amount, Version + 1);
}Local mutation可作为implementation optimization:builder在未发布、无alias的scope内修改,最后freeze/publish immutable value。边界保证比内部语法更重要。
Snapshot publication gate
Immutable state仍需atomic publication;两个writer若都从version 7计算version 8,最后写入者会静默覆盖另一个合法transition。Store必须把expected version纳入compare-and-swap contract,并让reader只看见完整old或完整new snapshot,绝不能看见构造中的中间对象。
var next = current.Apply(command);
if (!stateStore.TryReplace(current.Version, next))
return Conflict(current.Version);
return Accepted(next.Version);测试保留old reference并并发安排两个writer:旧对象始终不变,恰好一个replace成功,失败者reload后重新decide。若跨thread发布自定义结构,还要证明memory visibility由lock、volatile/Interlocked或并发容器提供;“没有setter”本身不建立happens-before关系。
↡Value构造后没有可达的unauthorized write path,所有change都产生受控new version。A short introduction to functional data structures
Persistent data structure通过structural sharing保留旧versions:更新只复制changed path,unchanged nodes复用。它不是disk persistence,而是version-preserving in-memory structure。Costs包括node indirection、allocation、cache locality和old-version retention。
↡Update产生new collection version并复用unchanged nodes,使旧version仍有效的数据结构技术。切换full copy、structural share、builder与retention
本章回顾:Change产生Version,Identity不等于Reference
- Mutation风险来自alias、ordering与ownership,不只是setter数量。
- Value、entity、event和reference有不同equality/identity semantics。
- Immutable state transition返回new version,persistence shell处理concurrency。
- Constructor/getter-only/immutable collection保护boundary;modern records不自动deep immutable。
- Structural sharing减少full copy但有allocation/retention成本,需measure。
练习
问题 1:readonly List field为什么仍可被修改?
问题 2:Entity equality应按全部fields还是id?
问题 3:什么时候local mutable builder可接受?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- alias-coupled mutation
- identity across versions
- immutability boundary
- structural sharing
- transient builder boundary
原版目录概念补充核对
以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。
Understanding state, identity, and change:机制、边界与证据
Chapter 9. Thinking about data functionally中的Understanding state, identity, and change应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。
Enforcing immutability:机制、边界与证据
Chapter 9. Thinking about data functionally中的Enforcing immutability应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。