Chapter 12. Stateful programs and stateful computations
覆盖explicit state transition、deterministic random generator与State computation,把hidden mutation重写为可组合、可复现的state threading。
学习目标
- 能分析hidden mutable state暴露的alias、ordering和concurrency问题,写出explicit transition signature
- 能实现seeded random generator与组合器,复现同seed同sequence并保留失败样本
- 能设计State computation的Map、Bind、Get、Put与Run,判断何时应改用普通fold或外部transaction
机制总览
Chapter 12. Stateful programs and stateful computations:机制路径
- 1
为什么Stateful不等于必须依赖Mutable Obj…
业务程序总会有state:账户余额、解析位置、随机数种子、购物车或workflow阶段。函数式方法不是否认state,而是让每次transition的输入、输出和ownership可见。隐藏field让signature看似 Command - Result ,真实行为却依赖之前调用、thread …
- 2
Programs that manage state
先盘点state ownership。Local state只在stack frame内,可用普通loop/builder;aggregate state由单一domain transition管理;shared state需要serialization/version;external state…
- 3
State machines and invariant …
复杂workflow应把phase建成discriminated states或受控constructors,使illegal transitions无法随意构造。Transition table记录from、command/event、guard、to与output;property tests验…
章级决策实验
Chapter 12. Stateful programs and stateful computations:机制与证据
切换《Chapter 12. Stateful programs and stateful computations》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。
选择推理阶段
当前阶段 · 为什么Stateful不等于必须依赖Mutable Obj…
业务程序总会有state:账户余额、解析位置、随机数种子、购物车或workflow阶段。函数式方法不是否认state,而是让每次transition的输入、输出和ownership可见。隐藏field让signature看似 Command - Result ,真实行为却依赖之前调用、thread …
可核验证据
以确定输入重复运行「为什么Stateful不等于必须依赖Mutable Obj…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
学完《Chapter 12. Stateful programs and stateful computations》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。
失效—证据矩阵
Chapter 12. Stateful programs and stateful computations:失效与核验
为什么Stateful不等于必须依赖Mutable Obj…
典型失效
若把「为什么Stateful不等于必须依赖Mutable Obj…」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。
核验证据
以确定输入重复运行「为什么Stateful不等于必须依赖Mutable Obj…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
Programs that manage state
典型失效
若把「Programs that manage state」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。
核验证据
以确定输入重复运行「Programs that manage state」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
State machines and invariant …
典型失效
若把「State machines and invariant …」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。
核验证据
以确定输入重复运行「State machines and invariant …」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。
为什么Stateful不等于必须依赖Mutable Object
业务程序总会有state:账户余额、解析位置、随机数种子、购物车或workflow阶段。函数式方法不是否认state,而是让每次transition的输入、输出和ownership可见。隐藏field让signature看似Command -> Result,真实行为却依赖之前调用、thread interleaving和global configuration;显式形式(State, Command) -> (State, Result)可独立复现每一步。
先预测:readonly field会让整个object immutable吗;同seed的PRNG两次一定生成同sequence吗;Random作为global singleton便于测试吗;State monad会自动保证database transaction吗;把任何loop改成Bind都会更清楚吗。答案都是否。
↡把previous state作为显式输入并返回new state,使transition不依赖隐藏调用历史的函数契约。Programs that manage state
先盘点state ownership。Local state只在stack frame内,可用普通loop/builder;aggregate state由单一domain transition管理;shared state需要serialization/version;external state由database/message protocol管理。不要把所有state都包装成同一种抽象。目标是把decision core保持deterministic,再由shell负责load、compare-and-swap和publish。
Either<Error, AccountState> Apply(
AccountState state,
Withdraw command) =>
command.Amount <= 0
? Left(Error.InvalidAmount)
: state.Balance < command.Amount
? Left(Error.InsufficientFunds)
: Right(state with
{
Balance = state.Balance - command.Amount,
Version = state.Version + 1
});Pure transition使table test可列old state + command -> new state/error,但并不自动解决并发。Shell仍要以expected version原子replace,失败后reload并重新decide。若state包含mutable list、clock或service reference,record copy仍只是shallow;需要immutable members、defensive copies和effect parameters,才能保证old snapshot不被后续路径修改。
切换hidden、explicit、command与shell
State machines and invariant evidence
复杂workflow应把phase建成discriminated states或受控constructors,使illegal transitions无法随意构造。Transition table记录from、command/event、guard、to与output;property tests验证余额不负、sequence单调、terminal state不再接受command等invariants。对于长链路,保存event/command trace和每步state digest,失败时能重放,不只看到最终assert。
Observability也应使用state版本和transition名称,而非dump完整对象。Metrics记录accepted/rejected/conflict;structured log记录entity id、from/to version、command id和reason code,敏感字段redact。这样运维可以区分业务拒绝、stale writer和code invariant fault,不会把所有失败归为“更新失败”。
A language for generating random data
Pseudo-random generator本质是deterministic state machine:Seed -> (Value, NextSeed)。把RNG state显式传递,就能构造ChooseInt、OneOf、ListOf、Frequency等小语言;组合器消费前一个next seed,不共享global Random。Production可从secure entropy取得initial seed,但测试必须记录seed,失败后用同seed重放。
public readonly record struct Gen<A>(
Func<Rng, (A Value, Rng Next)> Run);
Gen<(A, B)> Pair<A, B>(Gen<A> first, Gen<B> second) =>
new(rng =>
{
var (a, r1) = first.Run(rng);
var (b, r2) = second.Run(r1);
return ((a, b), r2);
});Generator distribution是contract。next % n可能有modulo bias;weighted choices总权重、range inclusivity和empty candidate都要定义。Property-based testing还需要shrinker把失败输入缩小,同时保留seed与generation path。若test只打印最后value而不打印seed,随机测试会变成不可复现噪声。
切换seed、next、compose与shrink
A general pattern for stateful computations
当多个步骤都具有S -> (A, S)形状时,手工传r1、r2会产生样板。State computation把它包装为值:Map只改A并传递S;Bind运行first,再用A选择second,并把first产生的state交给second;Get读取S,Put替换S,Modify应用transition。Run只在program边界提供initial state并观察final pair。
State<Cart, Receipt> checkout =
from cart in State.Get<Cart>()
from priced in Price(cart)
from _ in State.Put(cart.MarkCheckedOut())
select new Receipt(cart.Id, priced.Total);
var (receipt, finalCart) = checkout.Run(initialCart);Composition order是语义:交换两个Bind可能改变state和result,不能像independent applicative validation随意并行。State laws要求Pure不改state、Map保持identity、Bind满足identity/associativity的observable equivalence。测试要比较result与final state,并用recording state捕捉Get/Put sequence;只比较result会漏掉duplicate transition。
↡封装S到value和next S的计算,并以Map/Bind组合result dependency与state threading的抽象。切换Map、Bind、Get/Put与Run
Choosing between State, fold, and an effectful store
State abstraction适合parser position、generator seed、simulation和pure workflow,即整个program可在memory里一次Run。若只是把collection归约成accumulator,普通Aggregate更直接;若state需要跨request持久化、并发访问或外部transaction,State value不能替代repository/version protocol。可以在transaction内运行pure State program,但commit、retry和authorization仍由shell负责。
还要警惕超大computation graph和难读的nested generic types。Domain wrapper、named operations与局部query expression能保留意图;基础设施层再解释State。团队验收标准应包括stack behavior、allocation、traceability和debug step,不因抽象满足law就忽略运维成本。
Production gate: replay every transition
发布前为关键workflow生成golden traces:initial state、ordered commands、每步outcome/version、final digest。新版本对旧trace运行,预期semantic migration必须显式批准。并发integration test在store层注入conflict,证明失败者reload/redecide而非盲写;generator tests保存seed corpus,并检测distribution drift。
若state evolution需要schema migration,先读旧version到canonical state,再运行new transitions;不要让每个business function理解所有legacy shapes。Metrics对transition reason和conflict rate聚合,日志只保留可定位标识。这样pure core、state storage和migration各有清楚owner。
本章回顾:把History依赖写进Type和Test
- Stateful program可由explicit previous state驱动,不必依赖hidden mutable field。
- Pure transition与atomic publication解决不同问题,二者缺一都会留下错误。
- Random generator是seed state transition;可复现性依赖记录seed和generation path。
- State Map/Bind消除手工threading,并保留严格的transition order。
- State abstraction不能替代database transaction、versioning、migration和observability。
练习
问题 1:怎样把一个依赖mutable field的购物车Service改成pure core?
问题 2:Generator失败报告至少包含什么?
问题 3:什么时候State computation比Aggregate更差?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- explicit state transition
- state ownership boundary
- seeded generator
- State computation
- transition trace
原版目录概念补充核对
以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。
A language for generating random data:机制、边界与证据
Chapter 12. Stateful programs and stateful computations中的A language for generating random data应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。
A general pattern for stateful computations:机制、边界与证据
Chapter 12. Stateful programs and stateful computations中的A general pattern for stateful computations应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。