Chapter 11. Lazy computations, continuations, and the beauty of monadic composition

覆盖laziness、Try exception boundary与DB middleware continuation,把evaluation timing、failure和resource lifetime纳入可组合contract。

学习目标

  • 能区分eager、deferred、memoized与concurrent lazy evaluation,写出evaluation count和exception policy
  • 能设计Try adapter,把明确的throwing boundary提升为typed computation,同时保留unexpected fault、stack与cancellation语义
  • 能分析continuation组合的connection、transaction、commit/rollback和dispose,证明DB resource lifetime覆盖真正执行期

机制总览

Chapter 11. Lazy computations, continuations, and the beauty of monadic composition:机制路径

  1. 1

    为什么“现在算还是以后算”属于程序语义

    函数式组合不只关心输入输出值,还关心计算何时启动、是否重复、失败是否缓存、资源是否仍有效。把昂贵查询包装成function会延迟执行,却不自动memoize;把它包装成 Lazy<T> 通常共享一次结果,却又引入并发publication和异常缓存策略。Continuation则把“算完…

  2. 2

    The virtue of laziness

    Eager expression在construction point立刻求值;thunk Func<T> 把computation变成可调用值,每次调用通常重新执行;memoized lazy cell则在第一次demand后保存value。Laziness可避免未使用分支、建立inf…

  3. 3

    Laziness, caching, and invali…

    Memoization的key不只是function arguments,还隐含locale、clock、authorization、configuration和data version。对impure computation缓存会冻结旧world state;对failure缓存则可能把短暂故障永…

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 11. Lazy computations, continuations, and the beauty of monadic composition:机制与证据

切换《Chapter 11. Lazy computations, continuations, and the beauty of monadic composition》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么“现在算还是以后算”属于程序语义

函数式组合不只关心输入输出值,还关心计算何时启动、是否重复、失败是否缓存、资源是否仍有效。把昂贵查询包装成function会延迟执行,却不自动memoize;把它包装成 Lazy<T> 通常共享一次结果,却又引入并发publication和异常缓存策略。Continuation则把“算完…

可核验证据

以确定输入重复运行「为什么“现在算还是以后算”属于程序语义」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

学完《Chapter 11. Lazy computations, continuations, and the beauty of monadic composition》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 11. Lazy computations, continuations, and the beauty of monadic composition:失效与核验

为什么“现在算还是以后算”属于程序语义

典型失效

若把「为什么“现在算还是以后算”属于程序语义」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「为什么“现在算还是以后算”属于程序语义」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

The virtue of laziness

典型失效

若把「The virtue of laziness」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「The virtue of laziness」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

Laziness, caching, and invali…

典型失效

若把「Laziness, caching, and invali…」只写成函数式术语而不隔离副作用、状态和失败分支,组合后的程序仍会依赖隐藏时序,无法从输入稳定推导结果。

核验证据

以确定输入重复运行「Laziness, caching, and invali…」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么“现在算还是以后算”属于程序语义

函数式组合不只关心输入输出值,还关心计算何时启动、是否重复、失败是否缓存、资源是否仍有效。把昂贵查询包装成function会延迟执行,却不自动memoize;把它包装成Lazy<T>通常共享一次结果,却又引入并发publication和异常缓存策略。Continuation则把“算完以后做什么”变成参数,使外层可以围住内层的resource与outcome。

先预测:lambda创建时会执行body吗;同一个deferred function调用两次只查一次库吗;Lazy<T>失败后默认一定重试吗;Try应捕获所有Exception吗;transaction middleware能在返回deferred query前dispose connection吗。答案都是否。

The virtue of laziness

Eager expression在construction point立刻求值;thunk Func<T>把computation变成可调用值,每次调用通常重新执行;memoized lazy cell则在第一次demand后保存value。Laziness可避免未使用分支、建立infinite sequence、推迟昂贵I/O,但它也把failure和cost从定义处移动到消费处,reviewer必须追踪真正的evaluation boundary。

Func<Report> deferred = () => repository.BuildReport(day);
Lazy<Report> shared = new(
    () => repository.BuildReport(day),
    LazyThreadSafetyMode.ExecutionAndPublication);
 
var first = shared.Value;
var second = shared.Value; // same published result

IEnumerable&lt;T&gt;也是deferred recipe而非materialized collection。每次enumeration可能重新读文件、重查数据库或重新触发日志;Any()后再ToList()并非免费。API需说明single-use/multi-use、cold/hot、snapshot/live和thread safety。若consumers需要stable snapshot,就在owner boundary materialize并返回immutable view,不让调用者猜测。

分步1 / 3

切换eager、deferred、memoized与concurrent

Laziness, caching, and invalidation boundaries

Memoization的key不只是function arguments,还隐含locale、clock、authorization、configuration和data version。对impure computation缓存会冻结旧world state;对failure缓存则可能把短暂故障永久化。设计时写出cache identity、success TTL、negative-cache policy、eviction与single-flight。若value持有connection/stream等resource,缓存resource owner而非纯data通常是错误边界。

并发下需要区分at-most-one publication与at-most-one execution:某些publication mode允许多个threads同时运行factory,但只发布一个结果,这对pure CPU computation可接受,对扣款、发送消息不可接受。测试用barrier同时触发demand,并断言factory调用次数、最终identity和fault传播;不要只做顺序unit test。

Exception handling with Try

Try把可能throw的调用表示为deferred Try&lt;T&gt;,运行后得到success或failure。它的价值不是消灭exception,而是让expected composition显式:Map只转换success,Bind只在success继续,Recover按typed policy处理failure。捕获点必须窄,通常放在第三方parse、filesystem或DB driver adapter;pure domain函数仍返回Option/Either等业务outcome。

Try<Customer> load = Try.Of(() => gateway.Load(customerId));
 
Try<Invoice> invoice =
    load.Bind(customer =>
        Try.Of(() => renderer.Render(customer)));
 
var result = invoice.Run();

捕获Exception后还需taxonomy:argument/programmer bug通常不应变成可恢复Left;OperationCanceledException要保留cancellation;fatal runtime conditions不能被普通retry吞掉。Failure对象保存原cause或ExceptionDispatchInfo,boundary再映射stable external error。若只保存message,stack、inner cause和machine-readable type都丢失,运维无法定位。

分步1 / 3

切换success、expected、throw与cancel

Continuations make control flow a value

Continuation-passing style把函数从A -> B改写为接收“拿到B后如何继续”的参数,例如A -> (B -> R) -> R。外层不必把resource泄露给caller,而是把resource交给continuation并等待它完成,再统一commit/dispose。Map、Bind、Try和async都可看成受约束的continuation composition;关键是外层仍拥有lifetime与terminal outcome。

Creating a middleware pipeline for DB access

DB pipeline可分为open connection、begin transaction、run use case、commit or rollback、dispose五层。每层接收next并返回新function,composition order决定lifetime nesting。Business handler只依赖query/execute capability,不负责connection string、retry或transaction。最外层shell运行完整pipeline,result成功才commit,typed rejection与fault按policy rollback。

Func<DbConnection, Either<Error, T>> program = connection =>
    useCase(new SqlCustomerStore(connection));
 
Either<Error, T> RunInTransaction<T>(
    Func<DbConnection, Either<Error, T>> next)
{
    using var connection = Open();
    using var tx = connection.BeginTransaction();
    var result = next(connection);
    if (result.IsRight) tx.Commit(); else tx.Rollback();
    return result;
}

真实async版本必须await inner task以后才能dispose;返回IEnumerable&lt;T&gt;或callback也要确保enumeration/callback在scope内完成。Retry应包围完整transaction attempt并重新open/redecide,不能复用failed transaction。Nested middleware要明确ambient transaction、isolation level、timeout、idempotency与commit-uncertain情形,避免“数据库报错就重试”导致重复业务effect。

分步1 / 3

切换open、begin、commit与rollback

Production gate: prove timing, ownership, and retries

验收lazy/Try/middleware不能只比较最终value。Telemetry需记录attempt id、evaluation count、connection/transaction id、start/end、outcome category和retry reason;敏感SQL参数不可进入普通log。测试以fake clock、counter、barrier和recording adapter证明未使用分支不执行、shared lazy只按policy执行、cancellation不翻译为failure、resource总在inner完成后释放。

部署前再做一条真实integration path:让数据库在command执行后、commit response前断开,观察system如何处理unknown commit outcome。若command具有idempotency key,可查询结果后安全恢复;否则自动retry可能重复写入。Continuation让控制边界清晰,但可靠性仍来自明确protocol和证据。

本章回顾:Composition也必须保留Evaluation语义

  1. Laziness推迟计算,memoization才共享结果;两者不能混为一谈。
  2. Evaluation count、thread publication、exception caching与invalidation都是observable contract。
  3. Try只提升明确throwing boundary,不能吞掉programmer fault和cancellation。
  4. Continuation让外层拥有resource lifetime与terminal outcome,适合构造middleware。
  5. DB pipeline按acquire、transaction、use case、outcome、dispose嵌套,并用fault tests证明。

练习

问题 1:怎样证明一个Lazy factory在并发下符合约定?

问题 2:Try adapter应保留哪些failure evidence?

问题 3:Transaction middleware为何不能直接返回Deferred Query?

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

deferred evaluation contract
memoized computation
Try computation
continuation boundary
transaction middleware

原版目录概念补充核对

以下条目补齐官方目录中容易被示例主线掩盖的概念。它们不重复罗列目录,而是明确每项概念的机制、适用边界和验收证据。

Exception handling with Try:机制、边界与证据

Chapter 11. Lazy computations, continuations, and the beauty of monadic composition中的Exception handling with Try应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

Creating a middleware pipeline for DB access:机制、边界与证据

Chapter 11. Lazy computations, continuations, and the beauty of monadic composition中的Creating a middleware pipeline for DB access应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…