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. 1

    为什么Data最难的不是存什么,而是谁能改

    Mutable object把identity和current state放在同一个reference里;任何alias都可观察/触发变化,reasoning需要知道所有writers和order。Functional data view把state当value,change产生new versio…

  2. 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. 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是否永远不允许。答案都是否。

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。

分步1 / 3

切换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。

分步1 / 3

切换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关系。

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。

分步1 / 3

切换full copy、structural share、builder与retention

本章回顾:Change产生Version,Identity不等于Reference

  1. Mutation风险来自alias、ordering与ownership,不只是setter数量。
  2. Value、entity、event和reference有不同equality/identity semantics。
  3. Immutable state transition返回new version,persistence shell处理concurrency。
  4. Constructor/getter-only/immutable collection保护boundary;modern records不自动deep immutable。
  5. 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应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…