Ruby中如何将块内计算结果传递到块外?最优方案探讨
Ruby Enumerable 查找并返回计算值的优雅实现
现有一段无法得到预期结果的Ruby代码,目标是从Enumerable类型的coll中找到第一个满足element.calculate > 0的元素,并返回其计算后的值:
# 错误实现 def f(coll) pivot = coll.find do |element| t = element.calculate t > 0 end pivot && t # 无法访问块内的t,作用域不共享 end
这段代码无法运行的原因是:Ruby的块拥有独立的局部变量作用域,块内定义的t和块外的t不是同一个变量。
我尝试了以下几种修复方案,但都存在弊端:
- 方案一:重新计算找到元素的值
def f(coll) pivot = coll.find do |element| element.calculate > 0 end pivot && pivot.calculate end
弊端:找到目标元素后会重复执行一次calculate操作,存在性能浪费。
- 方案二:使用实例变量
def f(coll) pivot = coll.find do |element| @t = element.calculate @t > 0 end pivot && @t end
弊端:滥用实例变量,破坏了封装性,设计不够优雅。
- 方案三:先映射所有计算值再查找
def f(coll) coll.map(&:calculate).find {|x| x>0} end
弊端:会对coll中的所有元素执行calculate操作,哪怕第一个元素就符合条件,存在不必要的计算。
更优雅的实现方式
可以通过在块外部声明一个变量,利用Ruby块可以访问外部局部变量的特性,捕获计算结果,既避免重复计算,也不需要使用全局/实例变量:
def f(coll) calculated_val = nil coll.find do |element| calculated_val = element.calculate calculated_val > 0 end calculated_val end
这个实现的优势:
- 找到第一个符合条件的元素后立即停止遍历,不会多余计算后续元素
- 仅对目标元素执行一次
calculate操作,无重复计算 - 使用局部变量,作用域清晰,符合封装原则
关于binding的疑问
用binding来实现其实没有必要,反而会让代码复杂化。Ruby的块默认可以访问定义它时所在作用域的局部变量(只要变量在块定义前已声明),上述方案已经是最简洁直接的方式。如果一定要用binding,可以参考下面的写法,但显然不如上面的方案优雅:
def f(coll) b = binding coll.find do |element| b.local_variable_set(:t, element.calculate) b.local_variable_get(:t) > 0 end b.local_variable_get(:t) end
内容的提问来源于stack exchange,提问作者user1934428
相关产品推荐
相关产品推荐

