第21章 Proc类

比较block、Proc、lambda与Method的object、arity和return语义,掌握&block、to_proc、closure捕获以及Proc调用/组合边界。

学习目标

  • 能解释Proc类是什么与lambda表达式在“第21章 Proc类”中的责任边界
  • 能围绕“怎样解释 Proc、lambda、块参数转换与 return 的控制流差异?”运行正常与故障轨迹并定位首个分岔
  • 能用“第21章 Proc类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”

来源、版次与运行边界

“第21章 Proc类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单练习答案勘误只作为公开支持材料,不被冒充为原书全文。

对“第21章 Proc类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。

“第21章 Proc类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。

围绕“怎样解释 Proc、lambda、块参数转换与 return 的控制流差异?”,本页验收“第21章 Proc类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“把 lambda 与 Proc 的参数和 return 语义视为完全相同”;若无法定位第一处状态分岔,就拒绝当前解释。

从“Proc就是可保存的block”开始深化

Proc确实把behavior变成object,但Proc.new与lambda在arity和return control上不同;Method又绑定receiver。不能只写“响应call”。

先预测:lambda内return会返回哪里?只结束lambda call;普通Proc中的return可能尝试离开定义Proc的method,若method已返回会抛LocalJumpError。这个差异决定callback能否安全延迟。

Proc类是什么

Proc是可调用object,封装code与lexical environment。Proc.new {}proc {}通常创建non-lambda Proc,lambda {}/-> {}创建lambda Proc;lambda?可检查。影响memory和thread safety。

factor = 3
multiplier = Proc.new { |value| value * factor }
 
p multiplier.call(4)
factor = 5
p multiplier.call(4) # captured binding observes rebinding

Closure捕获binding而非immutable snapshot。需要snapshot时复制/冻结value并避免捕获整个self/request graph。Proc不可可靠跨process serialize,job queue存behavior identifier+data,不存Proc。

lambda表达式

Lambda检查arity更像method,return局部结束lambda;non-lambda Proc对缺/多positional args更宽松并可能nil填/丢弃。是选择依据。

strict = ->(left, right) { left + right }
loose = Proc.new { |left, right| [left, right] }
 
p strict.call(1, 2)
p loose.call(1)

Public callback pipeline通常偏好lambda或明确object,避免silent arity mismatch。Ruby 2.3 keyword与Hash转换会影响Proc forwarding,跨版本测试required。

通过Proc参数接收块

Method parameter &block把implicit block捕获成Proc,未给block时nil;调用处&callable执行to_proc并作为唯一block传入。只在要保存、转发或晚调用时捕获。

def build_transform(&block)
  raise ArgumentError, "block required" unless block
  ->(value) { block.call(value).to_s.strip }
end
 
transform = build_transform { |value| value.upcase }
p transform.call(" ruby ")

若method只是立即调用一次,直接yield更清楚。Captured block可能含non-local control;wrapper要测试异常/return。API最多一个block,但可额外接多个Proc positional/keywords,需命名角色。

to_proc方法

&object调用object.to_proc,Symbol#to_proc让items.map(&:upcase)把每个item作为receiver调用symbol method。不是任意魔法。

words = %w[ruby proc lambda]
p words.map(&:upcase)
 
upcase = :upcase.to_proc
p upcase.call("ruby")

Symbol shorthand适合无额外复杂policy的单method call;需要arguments、nil handling、logging或多步逻辑时写显式block。Custom to_proc要保持可预测arity/result,并避免每次转换创建重资源。

Proc的特征

Proc有identity,可放入Array/Hash、作为strategy/callback传递,闭包捕获locals。避免listener泄漏。

长期event subscription需返回unsubscribe token或owner close;否则Proc被publisher引用,连同captured graph永久存活。Weak reference并非默认解决方案,lifecycle应显式。

Proc side effects和reentrancy需说明:callback可否重复/并发调用,失败是否停止其它callbacks,注册期间能否修改列表。Copy callback list再iterate或使用成熟event abstraction。

Callback失败还要确定backtrace owner:同步call通常原样向caller传播;异步executor必须捕获、关联event/job id并交给error channel,不能让thread静默死亡。多个callbacks中一项失败,是fail-fast、继续并汇总,还是隔离重试,应由dispatcher contract决定。测试使用recording Proc记录arguments、次数和顺序,同时注入raise、slow与reentrant callback;不要只断言最终Array结果。

Cancellation也不是删除Array中Proc这么简单:调用可能已经开始,unsubscribe只保证未来不调还是等待in-flight结束,需要明确。涉及资源的callback最好实现closeable object而非裸Proc,使lifecycle可以被类型和tests观察。

Proc类的实例方法

call[].()执行;arity给参数数量编码,parameters提供kind/name introspection,source_location辅助诊断,binding/lambda?等按版本。禁止把reflection当完整validation。

operation = ->(left, right = 0) { left + right }
 
p operation.call(3, 4)
p operation[3]
p operation.arity
p operation.parameters
p operation.source_location

Negative arity编码optional/rest细节,具体解释按Ruby version。调用前手工检查arity常不如直接调用并让ArgumentError保留evidence;dynamic plugin boundary可用introspection给更早diagnostic,但仍跑contract tests。

curry与组合

Curry把多参数Proc变成分步application,Proc#>>/Proc#<< composition是较新版本特性,Ruby 2.3不应假定;可显式构造lambda pipeline。。

normalize = ->(text) { text.strip.downcase }
validate = ->(text) { raise ArgumentError, "empty" if text.empty?; text }
pipeline = ->(input) { validate.call(normalize.call(input)) }
 
p pipeline.call(" Ruby ")

当pipeline有conditional、state、retry、metrics和lifecycle时,命名class比嵌套Procs更可维护。Proc适合窄policy,不是消除domain objects的理由。

正式节点与章专属证据

  • :第 1 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
  • :第 2 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
  • :第 3 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
  • :第 4 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
  • :第 5 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
  • :第 6 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。

对象与状态模型

从输入、接收者到可观察证据

怎样解释 Proc、lambda、块参数转换与 return 的控制流差异?

输入与接收者

固定Proc类是什么所需的原始值、Ruby 版本和调用入口。

状态变化

在执行前记录接收者身份,并声明lambda表达式的允许状态。

观察证据

保存第21章 Proc类的初值、参数、编码或资源位置。

正式节点:Proc类是什么、lambda表达式、通过Proc参数接收块、to_proc方法、Proc的特征、Proc类的实例方法

控制与消息轨迹

在相同初值下定位首个分岔

  1. 01固定Proc类是什么的输入和接收者
  2. 02执行lambda表达式并记录状态
  3. 03观察通过Proc参数接收块的返回或副作用
  4. 04用Proc类的实例方法核对不变量并复位

运行不变量:参数严格度、调用位置、捕获变量和返回目标在执行前已声明。

边界故障探针

一次只破坏一个前提

基线满足:参数严格度、调用位置、捕获变量和返回目标在执行前已声明。

本章回顾:Behavior成为Object后,控制与生命周期更重要

  1. Block是一次call的implicit behavior,Proc/lambda是object,Method还绑定receiver。
  2. Lambda arity严格且return局部;普通Proc宽松且可能non-local return。
  3. &block只在保存/转发/晚调用时捕获,&object通过to_proc进入唯一block channel。
  4. Closure捕获bindings并延长graph lifetime;callback必须有owner、cancel、thread/reentry policy。
  5. Arity/parameters不是完整contract,组合还要保持shape、顺序、异常和side-effect次数。

练习与答案

练习

  1. 问题 1:建立正常轨迹。 回答“怎样解释 Proc、lambda、块参数转换与 return 的控制流差异?”,并写出四步执行记录。
  1. 问题 2:注入单一故障。 只制造“把 lambda 与 Proc 的参数和 return 语义视为完全相同”,应从哪里开始定位?
  1. 问题 3:覆盖正式节点。 用一个证据包串联Proc类是什么、lambda表达式、通过Proc参数接收块、to_proc方法、Proc的特征、Proc类的实例方法,说明为什么结论可由另一位读者独立复核。

名词解释

名词解释

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

Proc类是什么

“第21章 Proc类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

lambda表达式

“第21章 Proc类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

通过Proc参数接收块

“第21章 Proc类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

to_proc方法

“第21章 Proc类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

Proc的特征

“第21章 Proc类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

Proc类的实例方法

“第21章 Proc类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

资料与写作方式声明

本章以Ruby 基础教程第 5 版作者支持资料合法公开试读核定可见范围,并以目录限定未公开部分,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…