Chapter 4. Patterns in functional programming

覆盖Map、ForEach、Bind、Where、Option与IEnumerable组合及abstraction levels,以function shape和container effect重建核心FP patterns。

学习目标

  • 能比较Map、ForEach、Bind与Where的function shape、structure result和effect semantics,判断错误combinator
  • 能推导nested container与Bind flattening,设计Option、IEnumerable及cross-context组合的层级
  • 能分析primitive combinator、domain workflow与effect adapter三个abstraction levels,重构混杂pipeline

机制总览

Chapter 4. Patterns in functional programming:机制路径

  1. 1

    为什么先按Function Shape学习Pattern

    Map、Bind、Where不是神秘术语,而是反复出现的function/container shapes。看到 F 时先问传入function返回plain value、bool、effect还是另一个 F ,就能推导result structure。背extension names不如掌握sha…

  2. 2

    Applying a function to a stru…

    Map接收 T - R ,在不改变structure kind的前提下把 F 转成 F 。IEnumerable Map是Select,对每个element产生R;Option Map只在Some时调用,None保持None。Map不负责flatten,也不应执行与result无关的effect。

  3. 3

    Performing side effects with …

    ForEach遍历inner values执行side effect,返回void/Unit或原structure外的completion;它不是pure Map。把logging、DB write塞进Select会产生“result被忽略”的伪Map,execution timing还可能因deferred sequence变化。

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

章级决策实验

Chapter 4. Patterns in functional programming:机制与证据

切换《Chapter 4. Patterns in functional programming》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么先按Function Shape学习Pattern

Map、Bind、Where不是神秘术语,而是反复出现的function/container shapes。看到 F 时先问传入function返回plain value、bool、effect还是另一个 F ,就能推导result structure。背extension names不如掌握sha…

可核验证据

以确定输入重复运行「为什么先按Function Shape学习Pattern」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

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

失效—证据矩阵

Chapter 4. Patterns in functional programming:失效与核验

为什么先按Function Shape学习Pattern

典型失效

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

核验证据

以确定输入重复运行「为什么先按Function Shape学习Pattern」的最小管线,用属性测试、状态快照和副作用调用轨迹核对返回值、失败传播与资源边界。

Applying a function to a stru…

典型失效

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

核验证据

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

Performing side effects with …

典型失效

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

核验证据

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

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

为什么先按Function Shape学习Pattern

Map、Bind、Where不是神秘术语,而是反复出现的function/container shapes。看到F<T>时先问传入function返回plain value、bool、effect还是另一个F<R>,就能推导result structure。背extension names不如掌握shape,因为Option、IEnumerable、Task-like等contexts都复用这些关系。

先预测:ForEach是否等于Map;Map一个返回Option的function是否自动flatten;Where是否改变inner value type;Option和IEnumerable能否不决定outer context直接Bind;使用LINQ query syntax是否自动遵守monad laws。答案都是否。

Applying a function to a structure’s inner values

Map接收T -> R,在不改变structure kind的前提下把F<T>转成F<R>。IEnumerable Map是Select,对每个element产生R;Option Map只在Some时调用,None保持None。Map不负责flatten,也不应执行与result无关的effect。

Option<Customer> customer = FindCustomer(id);
Option<EmailAddress> email = customer.Map(value => value.Email);
 
IEnumerable<decimal> totals = orders.Select(CalculateTotal);
分步1 / 3

切换Map、ForEach、Bind与Where

Performing side effects with ForEach

ForEach遍历inner values执行side effect,返回void/Unit或原structure外的completion;它不是pure Map。把logging、DB write塞进Select会产生“result被忽略”的伪Map,execution timing还可能因deferred sequence变化。

Effect应该在pipeline terminal/boundary集中。Core先Map出commands,shell再ForEach解释;这样decision可test,effect次数和failure policy可审计。若需要收集每次effect outcome,返回Result values再sequence/traverse,不要用void丢信息。

Chaining functions with Bind

当function是T -> F<R>时,Map会得到F<F<R>>;Bind先Map再flatten一层,得到F<R>。Option Bind让None short-circuit,IEnumerable Bind/SelectMany把每个element产生的sequences串平。Flatten只适用于同类或有明确定义的composed context。

Option<Customer> customer = FindCustomer(id);
Option<Order> latest = customer.Bind(value => FindLatestOrder(value.Id));
 
IEnumerable<Line> lines = orders.SelectMany(order => order.Lines);
分步1 / 3

切换nested Map、Bind、sequence与cross-context

Filtering values with Where

Where接收T -> bool,保留满足predicate的values。IEnumerable可能从many变few/empty;Option Where在Some不满足时变None。它不解释为何被过滤,因此validation failure需要Either而不是Where,否则caller只能看到absence。

Predicate应尽量pure。若predicate调用remote service,enumeration次数会放大I/O并造成time-dependent membership。先materialize facts或把effectful decision建模为traversal/result,再纯filter。

Combining Option and IEnumerable with Bind

Cross-context组合必须选择shape。例如Option<IEnumerable<T>>表示整个sequence可能不存在;IEnumerable<Option<T>>表示每个element可能缺失。二者的Map/Bind行为、fallback与cardinality不同。先写domain sentence,再决定outer container。

IEnumerable<Order> orders = customerOption
    .Map(customer => repository.FindOrders(customer.Id))
    .IfNone(Enumerable.Empty<Order>());

把None转empty sequence是policy,会丢“customer missing”和“customer has no orders”的区别;只在consumer确实等价时做。否则保留Option到更外层或转Either error。

Coding at different levels of abstraction

Good pipeline的每一步处在相近level:Validate -> Price -> Reserve -> Respond表达domain workflow;内部才用Map/Bind/Where。若一行是business action,下一行是SelectMany plumbing,再下一行直接SQL,reader会不断切换mental model。

Named combinators可隐藏已证明的mechanics,不能隐藏effect。Effect adapters清楚标I/O,domain functions用业务名,generic primitives留在library layer。Composition root负责把三层接起来。

分步1 / 3

切换primitive、domain、effect与mixed levels

本章回顾:先看Arrow,再选Combinator

  1. Map用T-to-R保持structure,ForEach执行effect,Where按predicate过滤。
  2. Bind处理T-to-F-of-R并flatten同一context的一层。
  3. Option与IEnumerable组合前先决定outer semantic和absence/cardinality policy。
  4. Deferred execution会影响function/effect次数,所有operator都要测empty/Some/None/repeat。
  5. Domain、combinator与effect layers应各自清楚,再由composition连接。

练习

问题 1:为什么在Select里调用SendEmail是危险信号?

问题 2:Option<IEnumerable<Order>>IEnumerable<Option<Order>>如何选?

问题 3:怎样证明自定义Bind实现不是只会编译?

术语表

名词解释

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

combinator shape
structure-preserving map
effectful traversal
context layering
abstraction level

原版目录概念补充核对

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

Performing side effects with ForEach:机制、边界与证据

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

Chaining functions with Bind:机制、边界与证据

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

Filtering values with Where:机制、边界与证据

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

Combining Option and IEnumerable with Bind:机制、边界与证据

Chapter 4. Patterns in functional programming中的Combining Option and IEnumerable with Bind通过类型表达允许的输入、成功结果和失败分支;类型只编码已声明的不变量,不能替代运行时校验。应准备能编译与必须被拒绝的调用案例,并以成功、空值/缺失和业务失败样本核对每个分支都被显式处理。

Coding at different levels of abstraction:机制、边界与证据

Chapter 4. Patterns in functional programming中的Coding at different levels of abstraction应写成可组合的输入—输出契约,并把环境读取、状态改变与失败显式放在边界。用确定输入运行正常、空值和失败样本,同时记录返回值与副作用轨迹,证明结论不依赖隐藏状态。

讨论

评论区加载中…