第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 rebindingClosure捕获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_locationNegative 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的理由。
正式节点与章专属证据
- ↡在“第21章 Proc类”中,Proc类是什么必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
- ↡在“第21章 Proc类”中,lambda表达式必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
- ↡在“第21章 Proc类”中,通过Proc参数接收块必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
- ↡在“第21章 Proc类”中,to_proc方法必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
- ↡在“第21章 Proc类”中,Proc的特征必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
- ↡在“第21章 Proc类”中,Proc类的实例方法必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“参数严格度、调用位置、捕获变量和返回目标在执行前已声明。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样解释 Proc、lambda、块参数转换与 return 的控制流差异?
输入与接收者
固定Proc类是什么所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明lambda表达式的允许状态。
观察证据
保存第21章 Proc类的初值、参数、编码或资源位置。
正式节点:Proc类是什么、lambda表达式、通过Proc参数接收块、to_proc方法、Proc的特征、Proc类的实例方法
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定Proc类是什么的输入和接收者
- 02执行lambda表达式并记录状态
- 03观察通过Proc参数接收块的返回或副作用
- 04用Proc类的实例方法核对不变量并复位
运行不变量:参数严格度、调用位置、捕获变量和返回目标在执行前已声明。
边界故障探针
一次只破坏一个前提
本章回顾:Behavior成为Object后,控制与生命周期更重要
- Block是一次call的implicit behavior,Proc/lambda是object,Method还绑定receiver。
- Lambda arity严格且return局部;普通Proc宽松且可能non-local return。
- &block只在保存/转发/晚调用时捕获,&object通过to_proc进入唯一block channel。
- Closure捕获bindings并延长graph lifetime;callback必须有owner、cancel、thread/reentry policy。
- Arity/parameters不是完整contract,组合还要保持shape、顺序、异常和side-effect次数。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样解释 Proc、lambda、块参数转换与 return 的控制流差异?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“把 lambda 与 Proc 的参数和 return 语义视为完全相同”,应从哪里开始定位?
- 问题 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类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。