Chapter 7. Structuring an application with functions

覆盖partial application、method resolution、currying、partial-friendly API、application modularization与fold,把dependency wiring变成typed function construction。

学习目标

  • 能区分partial application与currying,推导固定stable dependencies后得到的workflow signature
  • 能分析C# overload/method-group inference与parameter order,设计partial-application-friendly API
  • 能设计composition root、module functions和Fold reduction,复现empty/list/state aggregation semantics

机制总览

Chapter 7. Structuring an application with functions:机制路径

  1. 1

    为什么Application Structure可以是Fu…

    传统application用mutable service objects保存dependencies;functional approach也需要dependency wiring,只是把dependencies作为parameters,再在startup部分应用,得到只接request的func…

  2. 2

    Partial application: supplyin…

    给 (Config, Store, Request) - Response 先传Config/Store,得到 Request - Response 。稳定dependencies在composition root固定,变化data在runtime传入。Closure保存dependency ref…

  3. 3

    Overcoming the quirks of meth…

    C method group不是独立value,需target delegate决定overload/generic inference。Partial helper遇overloaded method时可能ambiguous;用显式 Func 、adapter lambda或domain-name…

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

章级决策实验

Chapter 7. Structuring an application with functions:机制与证据

切换《Chapter 7. Structuring an application with functions》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么Application Structure可以是Fu…

传统application用mutable service objects保存dependencies;functional approach也需要dependency wiring,只是把dependencies作为parameters,再在startup部分应用,得到只接request的func…

可核验证据

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

学完《Chapter 7. Structuring an application with functions》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 7. Structuring an application with functions:失效与核验

为什么Application Structure可以是Fu…

典型失效

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

核验证据

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

Partial application: supplyin…

典型失效

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

核验证据

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

Overcoming the quirks of meth…

典型失效

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

核验证据

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

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

为什么Application Structure可以是Function Construction

传统application用mutable service objects保存dependencies;functional approach也需要dependency wiring,只是把dependencies作为parameters,再在startup部分应用,得到只接request的function。最终handler仍可交给framework,core无需service locator。

先预测:partial application是否等于currying;任何method group都可自动partial;dependency放最后是否一样好;closure捕获singleton是否没有lifetime;Aggregate无seed是否总安全。答案都是否。

Partial application: supplying arguments piecemeal

(Config, Store, Request) -> Response先传Config/Store,得到Request -> Response。稳定dependencies在composition root固定,变化data在runtime传入。Closure保存dependency references,因此lifetime必须不短于result function;不要把request-scoped resource捕获进singleton handler。

Func<Request, Response> BuildHandler(Config config, IStore store) =>
    request => Handle(config, store, request);
分步1 / 3

切换partial、currying、method group与closure

Overcoming the quirks of method resolution

C# method group不是独立value,需target delegate决定overload/generic inference。Partial helper遇overloaded method时可能ambiguous;用显式Func<...>、adapter lambda或domain-named wrapper固定signature。不要用大量casts掩盖API shape不清。

新增overload可能让原本可编译的method group在rebuild后变得ambiguous;参数改名也会破坏named arguments。对公共composition boundary定义稳定delegate alias和named adapter,并用compile fixture覆盖旧调用形状,API演进时先证明resolution仍唯一。

Curried functions: optimized for partial application

Currying把(A,B,C)->R转换为A->B->C->R,每次application自然返回下一function。Partial application是使用行为,currying是function representation transformation。C#原生method通常uncurried,library helper可Curry/Apply;过度nested Func会损害debug/readability。

Func<TaxPolicy, Func<Currency, Func<Order, Price>>> price =
    tax => currency => order => Price.Calculate(order, tax, currency);
 
Func<Order, Price> priceUsd = price(usTax)(Currency.Usd);

Creating a partial-application-friendly API

参数顺序优先stable dependency/config,再放per-call data。避免optional flags和同type primitive;给overload提供one canonical function shape。Async/cancellation通常属于per-call context,不能在startup固定一个过期token。

分步1 / 3

切换dependency-first、data-first、overload与named function

Modularizing and composing an application

Module可以暴露builders/functions:ValidationModule.Build(config)、Workflow.Build(validate, decide, execute)。每个module返回最小delegate contract,composition root集中选择implementations。Domain workflow不依赖container APIs,test传入pure/fake functions。

var validate = Validation.Build(rules);
var decide = Pricing.Build(taxPolicy);
var execute = Execution.Build(storeFactory, clock);
var handler = Workflow.Build(validate, decide, execute);

Reducing a list to a single value

Fold/Aggregate以seed和step把sequence归约为一个value。Seed定义empty result与accumulator type;step应明确ordering、associativity与effect。Sum、state replay、function composition都可视为fold。无seed Aggregate对empty partial,不适合需要total behavior的public workflow。

分步1 / 3

切换sum、state、compose与empty

本章回顾:在Startup构造Behavior,在Runtime提供Data

  1. Partial application固定部分arguments;currying改变multi-arg representation。
  2. C# method groups需target type,overload ambiguity用adapter/canonical signature解决。
  3. Dependency-first order让composition root自然构造request handlers。
  4. Captured dependencies必须满足function lifetime,scoped resources使用factory/per-call owner。
  5. Fold以seed和step表达total reduction及其laws。

练习

问题 1:为什么不应在singleton handler中capture DbContext?

问题 2:Overloaded Parse无法传给Map时如何修?

问题 3:Event fold为什么必须有initial state?

术语表

名词解释

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

partial application
method-resolution boundary
curried function shape
functional composition root
fold reduction

原版目录概念补充核对

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

Overcoming the quirks of method resolution:机制、边界与证据

Chapter 7. Structuring an application with functions中的Overcoming the quirks of method resolution应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

Creating a partial-application-friendly API:机制、边界与证据

Chapter 7. Structuring an application with functions中的Creating a partial-application-friendly API应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

Modularizing and composing an application:机制、边界与证据

Chapter 7. Structuring an application with functions中的Modularizing and composing an application应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

Reducing a list to a single value:机制、边界与证据

Chapter 7. Structuring an application with functions中的Reducing a list to a single value应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…