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

    为什么Stateful不等于必须依赖Mutable Obj…

    业务程序总会有state:账户余额、解析位置、随机数种子、购物车或workflow阶段。函数式方法不是否认state,而是让每次transition的输入、输出和ownership可见。隐藏field让signature看似 Command - Result ,真实行为却依赖之前调用、thread …

  2. 2

    Programs that manage state

    先盘点state ownership。Local state只在stack frame内,可用普通loop/builder;aggregate state由单一domain transition管理;shared state需要serialization/version;external state…

  3. 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都会更清楚吗。答案都是否。

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不被后续路径修改。

分步1 / 3

切换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显式传递,就能构造ChooseIntOneOfListOfFrequency等小语言;组合器消费前一个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,随机测试会变成不可复现噪声。

分步1 / 3

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

分步1 / 3

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

  1. Stateful program可由explicit previous state驱动,不必依赖hidden mutable field。
  2. Pure transition与atomic publication解决不同问题,二者缺一都会留下错误。
  3. Random generator是seed state transition;可复现性依赖记录seed和generation path。
  4. State Map/Bind消除手工threading,并保留严格的transition order。
  5. 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应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…